中文文檔:https://www.redux.org.cn/
結合 阮一峯 大神 redux 教程
1、適用場景:多交互、多數據源
- 用戶的使用方式複雜
- 不同身份的用戶有不同的使用方式(比如普通用戶和管理員)
- 多個用戶之間可以協作
- 與服務區大量交互,或者使用了WebSocket
- View要從多個來源獲取數據
2、設計思想
- Web應用是一個狀態機,視圖與狀態是一一對應的
- 所有的狀態,保存在一個對象裏面
3、基本概念與API
- Action (可以理解爲View)
State 的變化會導致 View 的變化,而 Action 就是 View 發出的通知,表示 State 要發生變化了。
也可以理解爲: Action 描述當前發生的事情。改變 State 的唯一辦法,就是使用 Action ,它會運送數據到 Store 。
通過 store.dispatch() 方法將 action 傳到 store。
- Action 創建函數(注意: “action” 和 “action 創建函數”是兩個概念)
定義一個函數來生成 Action ,這個函數就叫 Action 創建函數
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
上面的 addTodo 函數就是一個 Action 創建函數
Store 收到 Action 後,必須給出一個新的 State,這樣 View 纔會發生變化。這種 State 的計算過程就叫做 Reducer 。
Reducer 是一個純函數,接收舊的 State 和 Action 作爲參數,返回一個新的 State。
const reducer = function (state, action) {
return newState
}
整個應用的狀態的初始狀態,可以作爲 State 的默認值。例:
const initState = 0;
const reducer = (state = initState, action) => {
switch(action.type) {
case 'ADD':
return state + action.payload;
default:
return false;
}
}
const state = reducer(1, {
type: 'ADD',
payload: 2
})
// console.log(state) => 3
- 純函數
純函數是函數式編程的概念
1. 不得改寫參數
2. 不能調用系統 I/O 的 API
3. 不能調用 Date.now() 或者 Math.random() 等不純方法
由於 Reducer 是純函數,可以保證同樣的 State 可以得到同樣的 View 。因此 Reducer 函數裏面不能改變 State ,必須返回一個全新的對象,例:
// State 是一個對象
function reducer(state, action) {
return Object.assign({}, state, {thingToChange})
// 或者
return {...stete, ...newState}
}
// State 是一個數組
function reducer(state, action) {
return [...state, newItem];
}
- Store 是保存數據的地方,整個應用只有一個 Store。職責:
1. 維持應用的 State
2. 提供 getState() 方法獲取 state
3. 提供 dispatch(action) 方法更新 state
4. 通過 subscribe(listener) 註冊監聽器
5. 通過 subscribe(listener) 返回的函數註銷監聽器
根據已有的 reducer 來創建 store 非常容易
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
// createStore(fn)
createStore 函數接受另一個函數作爲參數,返回新的 Store 對象。
注:更多詳細的內容請看官網或者阮一峯大神的redux入門教程
例: 登錄案例
描述: 使用 redux 進行登錄狀態的管理,使用 redux-persist 進行持久化存儲,react-redux 與 redux 一起使用,react-navigation 頁面之間的跳轉
目錄結構
- ActionTypes.js
'use strict'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_ERROR = 'LOGOUT_ERROR';
- LoginAction.js
'use strict'
import {
LOGIN_SUCCESS,
LOGIN_ERROR,
LOGOUT_SUCCESS,
LOGOUT_ERROR
} from './ActionTypes'
export function loginSuccess(user) {
return {
type: LOGIN_SUCCESS,
isLogin: true,
message: '登錄成功',
user
}
}
export function loginError(user) {
return {
type: LOGIN_ERROR,
isLogin: false,
message: '登錄失敗',
user
}
}
export function logoutSuccess(user) {
return {
type: LOGOUT_SUCCESS,
isLogin: false,
message: '退出成功',
user
}
}
export function logoutError(user) {
return {
type: LOGOUT_ERROR,
isLogin: false,
message: '退出失敗',
user
}
}
- LoginReducer.js
'use strict'
import _ from 'lodash'
import {
LOGIN_ERROR,
LOGIN_SUCCESS,
LOGOUT_ERROR,
LOGOUT_SUCCESS,
} from '../actions/ActionTypes'
export default function loginReducer(state = null, action) {
switch(action.type) {
case LOGIN_SUCCESS:
return _.assign({}, state, {
isLogin: true,
message: '登錄成功',
user: action.user,
})
case LOGIN_ERROR:
return _.assign({}, state, {
isLogin: false,
message: '登錄失敗',
user: action.user,
})
case LOGOUT_SUCCESS:
return _.assign({}, state, {
isLogin: false,
message: '退出成功',
user: null,
})
case LOGOUT_ERROR:
return _.assign({}, state, {
isLogin: false,
message: '退出失敗',
user: action.user,
})
default:
return state
}
}
- RootReducer.js
'use strict'
import { combineReducers } from 'redux'
import LoginReducer from './LoginReducer'
const rootReducer = combineReducers({
LoginReducer,
})
export default rootReducer
- store.js
import { createStore } from 'redux'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage' //defaults to localStorage for web and AsyncStorage for react-native
import RootReducer from './reducers/RootReducer'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, RootReducer)
export const store = createStore(persistedReducer);
export const persistor = persistStore(store);
- App.js
import React, {Component} from 'react';
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './redux/store'
import { createStackNavigator, createAppContainer } from 'react-navigation'
import Home from './src/Home'
import Login from './src/Login'
const RootStack = createStackNavigator(
{
Home,
Login,
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
header: null
}
}
)
const AppContainer = createAppContainer(RootStack);
export default class Navigation extends Component {
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor} loading={null}>
<AppContainer/>
</PersistGate>
</Provider>
)
}
}
- Home.js
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
Button,
} from 'react-native';
import { connect } from 'react-redux'
import { logoutSuccess } from '../redux/actions/LoginAction'
class Home extends Component {
constructor(props) {
super(props);
this.state = {
user: {}
}
}
componentDidMount() {
let user = this.props.user;
this.setState({user})
}
render() {
let user = this.props.user;
return (
<View style={styles.container}>
<Text style={styles.welcome}>登錄狀態: {user ? '已登錄' : '未登錄'}</Text>
{
user ?
<View>
<Text style={styles.welcome}>姓名:{user.name}</Text>
<Text style={styles.welcome}>年齡:{user.age}</Text>
</View>
:
null
}
{
user ?
<View>
<Button title={'退出'}
onPress={()=>this.props.dispatch(logoutSuccess())}
/>
<Button title={'前往登錄頁修改信息'}
onPress={()=>this.props.navigation.navigate('Login')}
/>
</View>
:
<Button title={'前往登錄'}
onPress={()=>this.props.navigation.navigate('Login')}
/>
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
function selected(state) {
return user = state.LoginReducer;
}
export default connect(selected)(Home)
- Login.js
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
Button,
} from 'react-native';
import { connect } from 'react-redux'
import { loginSuccess } from '../redux/actions/LoginAction'
class Login extends Component {
constructor(props) {
super(props);
this.state = {
user: {
name: '',
age: '',
}
}
}
componentDidMount() {
let user = this.props.user;
if(user) {
this.setState({user})
}
}
render() {
let user = this.state.user;
return (
<View style={styles.container}>
<TextInput
placeholder={'姓名'}
defaultValue={user.name}
style={styles.input}
onChangeText={(text) => this._change('name',text)}
/>
<TextInput
placeholder={'年齡'}
defaultValue={user.age}
style={{borderWidth:1,borderColor:'gray',padding:5,marginBottom:2}}
onChangeText={(text) => this._change('age',text)}
/>
<Button title={'登錄'}
onPress={this._login}
/>
<Button title={'前往首頁'}
onPress={()=>this.props.navigation.navigate('Home')}
/>
</View>
);
}
_change = (key,value) => {
let user = this.state.user;
user[key] = value;
this.setState({user});
}
_login = () => {
let user = this.state.user;
this.props.dispatch(loginSuccess(user));
this.props.navigation.push('Home')
}
}
const styles = StyleSheet.create({
container: {
marginTop: 50,
paddingHorizontal: 20,
backgroundColor: '#F5FCFF',
},
input: {
borderWidth: 1,
borderColor: 'gray',
padding: 5,
marginBottom: 2,
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
function selected(state) {
return user = state.LoginReducer;
}
export default connect(selected)(Login)
實現效果