Redux 中文文檔 在此. 如果想找具體可操作的案例, 文檔里面都有. 文末有彩蛋.
為什么會(huì)有 Redux ?
在 iOS 中, 隨著項(xiàng)目迭代, 功能越來越復(fù)雜, 如果還是采用 MVC 架構(gòu), 由于 Controller 內(nèi)部的職責(zé)太多, 而導(dǎo)致代碼塊耦合嚴(yán)重, 不利于測試和維護(hù), 由此, MVVM 應(yīng)運(yùn)而生.
在 MVVM 架構(gòu)中, 通過將表現(xiàn)邏輯和交互邏輯移到 view-model 中, 借助 RxSwift 等響應(yīng)式編程的框架, controller 監(jiān)聽 view-model 中的 view state 的變更, 而做出對(duì)應(yīng)的操作, 比如修改 view.

協(xié)調(diào)器持有對(duì) model 層的引用, 并且了解 view controller 樹的結(jié)構(gòu), 這樣, 它能夠?yàn)槊總€(gè)場景的 view-model 提供所需要的 model 對(duì)象. 如果不增加協(xié)調(diào)器, 那么 view controller 間就會(huì)有耦合.
實(shí)際項(xiàng)目中是否引入?yún)f(xié)調(diào)器, 得看具體情況. 如果是針對(duì)不是那么復(fù)雜的功能做重構(gòu), 太復(fù)雜的架構(gòu)反而是畫蛇添足.
回到 React Native 項(xiàng)目, 如果 JavaScript 單頁應(yīng)用功能越來越復(fù)雜, 我們同樣要處理功能模塊解耦, 更細(xì)一點(diǎn), 處理各種變化的 state. 這些 state 可能包括服務(wù)器響應(yīng)數(shù)據(jù), 緩存數(shù)據(jù), 也包括 UI 狀態(tài), 如被選中的標(biāo)簽, 是否顯示加載動(dòng)效或者分頁器等等.
所以我們選擇了 Redux.
Redux 是什么 ?
Redux 是 JavaScrip 狀態(tài)容器, 提供可預(yù)測化的 state 管理.
在 Redux 中, 這些 state , 也可以稱之為 model 數(shù)據(jù).
通過 action(交互邏輯, 顯示邏輯), 更改不同的 state, 最后顯示在界面上.
在下面代碼中, POPULAR_REFRESH 和 POPULAR_REFRESH_SUCCESS 代表兩種 action , 對(duì)于不同的 action, 內(nèi)部需要傳遞的 state 數(shù)據(jù)也不同, 最終傳遞到 JavaScript 頁面, 映射到 props 中, 做最后的處理.
case Types.POPULAR_REFRESH: //下拉刷新中
return {
...state,
[action.storeName]: { // storeName 是類似于 java, ios等這些tab, 它是動(dòng)態(tài)的
...state[action.storeName],
refreshState: 1,
}
};
case Types.POPULAR_REFRESH_SUCCESS: //下拉刷新成功
return {
...state,
[action.storeName]: {
...state[action.storeName],
items: action.items, //原始數(shù)據(jù)
projectModels: action.projectModels, // 此次要展示的數(shù)據(jù)
refreshState: 0, // 默認(rèn)
pageIndex: action.pageIndex
}
};
Redux 的工作流程

-
- 用戶操作View, 通過
dispatch方法, 發(fā)出Action.
-
Action可以是網(wǎng)絡(luò)請(qǐng)求, 交互邏輯等.
- 用戶操作View, 通過
-
-
Store自動(dòng)調(diào)用Reducer, 并且傳入兩個(gè)參數(shù)(當(dāng)前State和收到的Action),Reducer會(huì)返回新的State.
- 如果有
Middleware,Store會(huì)將當(dāng)前State和收到的Action傳遞給Middleware,Middleware會(huì)調(diào)用Reducer然后返回新的State.
-
-
State一旦有變化,Store就會(huì)調(diào)用監(jiān)聽函數(shù), 更新View.
-
在整個(gè)流程中, 數(shù)據(jù)都是單向流動(dòng)的.
Redux 的三原則
-
Redux應(yīng)用中所有的state都以一個(gè)對(duì)象樹的形式存儲(chǔ)在一個(gè) 單一 的store中. -
state是 只讀 的: 唯一改變state的辦法是觸發(fā)action,action是一個(gè)描述發(fā)生什么的對(duì)象. - 使用純函數(shù)來執(zhí)行修改: 為了描述
action如何改變state樹, 你需要編寫reducers.
reducer是形式為(state, action) => state的純函數(shù). 根據(jù)action修改state, 將其轉(zhuǎn)變?yōu)橄乱粋€(gè)state.
Redux 在 React Native 中的應(yīng)用
準(zhǔn)備工作
根據(jù)需要, 安裝以下組件.
- redux(必選).
- react-redux(必選): redux 作者開發(fā)的一個(gè)在 React 上使用的 redux 庫.
- redux-devtools(可選): Redux 開發(fā)者工具, 支持熱加載, action 重放, 自定義 UI 等功能.
- redux-thunk(可選): 實(shí)現(xiàn) action 異步的 middleware.
- redux-persist(可選): 支持 store 本地持久化.
- redux-observable(可選): 實(shí)現(xiàn)可取消的 action.
安裝方式
yarn add redux react-redux redux-devtools
react-redux 介紹
react-redux 是 Redux 官方提供的 React 綁定庫.
有幾個(gè)位置需要注意:
-
<Provider> 組件: 這個(gè)組件需要包裹在整個(gè)組件樹的最外層(根組件). 讓所有的子組件都能使用
connect()方法綁定store. -
connect(): 這是
react-redux提供的一個(gè)方法, 如果一個(gè)組件想要響應(yīng)狀態(tài)的變化, 就需要把自己作為參數(shù)傳給connect()的結(jié)果,connect()方法會(huì)處理與store綁定的細(xì)節(jié), 并通過selector確定該綁定store的哪一部分的數(shù)據(jù). -
selector: 這是我們自定義的函數(shù), 這個(gè)函數(shù)聲明了你的組件需要整個(gè)
store中的哪一部份數(shù)據(jù)作為自己的props. -
dispatch: 每當(dāng)需要改變應(yīng)用中的
state, 都需要dispatch一個(gè)action.
使用步驟
1. 創(chuàng)建 action
定義 action 類型
// 各種 action 類型
export const THEME_CHANGE = 'THEME_CHANGE'
export const POPULAR_REFRESH = 'POPULAR_REFRESH'
// 各種 action 類型
export default {
THEME_CHANGE: "THEME_CHANGE",
POPULAR_REFRESH: "POPULAR_REFRESH"
}
創(chuàng)建 action 函數(shù)
import Types from '../types';
export function onThemeChange(theme) {
// 同步 action
// return {
// type: Types.THEME_CHANGE,
// theme: theme,
// }
// 異步 action 需要引入 'redux-thunk'
return dispatch => {
dispatch({
type: Types.THEME_CHANGE,
theme: theme,
})
};
}
注意:
- 這里我們傳入了一個(gè)參數(shù) theme, 是我們將要修改的主題樣式.
-
action既可以同步實(shí)現(xiàn), 也可以異步實(shí)現(xiàn). 對(duì)于網(wǎng)絡(luò)請(qǐng)求, 數(shù)據(jù)庫加載等應(yīng)用場景, 我們必須使用異步action, - 異步
action可以理解為, 在action內(nèi)部進(jìn)行異步操作, 等操作返回后, 在dispatch一個(gè)action. - 為了使用異步
action, 我們需要引入redux-thunk庫. 將異步中間件添加到store中.
import thunk from 'redux-thunk'
const middlewares = [
thunk,
middleware2,
middleware3,
];
export default createStore(reducers, applyMiddleware(...middlewares));
默認(rèn)情況下,
createStore()所創(chuàng)建的Reduxstore沒有使用middleware, 所以只支持同步數(shù)據(jù)流.我們可以使用
applyMiddleware()來增強(qiáng)createStore(), 添加thunk這類中間件來實(shí)現(xiàn)異步 action.像
redux-thunk或redux-promise這類支持異步的 moddleware 都包裝了store的dispatch()方法. 因此我們可以dispatch一些除了action以外的內(nèi)容. 例如函數(shù)或者 Promise.注意: 當(dāng)
middleware鏈中的最后一個(gè)moddleware開始dispatchaction時(shí), 這個(gè)action必須是一個(gè)普通對(duì)象.
2. 創(chuàng)建 reducers
reducer 是根據(jù) action 類型 修改 state, 將其轉(zhuǎn)變成下一個(gè) state. 這里面根據(jù)實(shí)際的需要, 定義了各種不同的 state 樹.
import Types from '../../action/types';
const defaultState = {
theme: 'red'
}
export default function onAction(state=defaultState, action) {
switch (action.type) {
case Types.THEME_CHANGE:
return {
...state,
theme: action.theme,
}
default:
return state;
}
}
注意
- reducer 是一個(gè)純函數(shù), 他僅僅用于返回下一個(gè) state, 為了保證 reducer 盡可能簡單, 我們不能在這里面改變 state, 只能在 action 創(chuàng)建函數(shù) 內(nèi)部做.
- reducer 內(nèi)部也不要調(diào)用非純函數(shù),
Date.now()或Math.random()這種. - 在默認(rèn)的情況下, 要返回舊的 state. 以應(yīng)對(duì)未知 action 的情況.
- 對(duì)于獨(dú)立 page 的 reducer, 我們應(yīng)該針對(duì)各個(gè)頁面進(jìn)行拆分, 以免 action 太多. 導(dǎo)致不容易維護(hù). 拆分完我們需要合并進(jìn)行使用.
import {combineReducers} from 'redux';
import theme from './theme';
import popular from './popular';
// 合并 reducer
const index = combineReducers({
themeReducer: theme,
popularReducer: popular,
})
export default index;
-
combineReducers()所做的只是生成一個(gè)函數(shù), 這個(gè)函數(shù)來調(diào)用你的一系列 reducer, 每個(gè) reducer 根據(jù)他們的 key 來篩選出 state 中的一部分?jǐn)?shù)據(jù)并處理, 然后這個(gè)生成的函數(shù)再將所有 reducer 的結(jié)果合并成一個(gè)大的對(duì)象. 如果combineReducers()所包含的所有 reducers 都沒有更改 state, 那么就不會(huì)創(chuàng)建一個(gè)新的對(duì)象.
3. 使用 store
Store 是 存儲(chǔ) state 的容器.
- 它會(huì)把兩個(gè)參數(shù)(當(dāng)前的 state 樹 和 action) 傳入 reducer
- reducer 會(huì)把新的 state 返回給 store,
- store 更新 state 到 view 中.
store 里有幾個(gè)方法
- 提供
getState()方法獲取 state. - 提供
dispatch(action)方法更新 state. - 通過
subscribe(listener)注冊(cè)監(jiān)聽器. - 通過
subscribe(listener)返回的函數(shù), 注銷監(jiān)聽器.
配置 store
import {createStore, applyMiddleware} from 'redux';
import reducers from '../reducer/reducer';
import thunk from 'redux-thunk'
const logger = store => next => action => {
if (typeof action === 'function') {
console.log('dispatching a function')
} else {
console.log('dispatching', action)
}
console.log('nextState', store.getState());
};
const middlewares = [
logger, // 打印 state 信息
thunk, // 提供異步 action
];
// 在 store 中添加中間件, 配置 reducer
export default createStore(reducers, applyMiddleware(...middlewares));
使用 store, 我們首先要引入 react-redux 庫, 在 App 的根組件, 通過 <Provider/> 配置 store.
import {Provider} from 'react-redux';
import store from './js/store';
export default class App extends Component {
render() {
return (
<Provider store={store}>
<AppNavigators />
</Provider>
)
}
}
3. 在組件中應(yīng)用 Redux
訂閱 state, dispatch
import {connect} from 'react-redux';
import actions from '../action/index';
class HomePage extends Component {
render() {
return (
<View style={styles.container}>
<Button
title='改變主題'
onPress={()=> {
this.props.onThemeChangeProp('orange')
}}
/>
</View>
)
}
}
const mapStateToProps = state => ({
themeProp: state.themeReducer.theme
});
const mapDispatchToProps = dispatch => ({
onThemeChangeProp: theme => dispatch(actions.onThemeChange(theme)),
});
export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
在上述代碼中, 我們訂閱了 store 中的 theme state 和 dispatch,
- 我們通過
react-redux提供的connect()方法, 將 store 中的目標(biāo) state, 和處理該 state 的 action 所在的 selector 傳入其中. - 并且將目標(biāo)組件傳入
connect()的結(jié)果中, 使得目標(biāo)組件響應(yīng) state 的變化. - 這樣該組件就可以通過 props 取出對(duì)應(yīng)的state, 和操作 action.
react-navigation + Redux
如果你是通過 react-navigation 做 App 的基礎(chǔ)架構(gòu), Redux 也對(duì)其做了支持.
我們需要導(dǎo)入 react-navigation-redux-helpers 庫.
1. 配置 navigator
import {
createAppContainer, createStackNavigator, createSwitchNavigator
}
from 'react-navigation';
import HomePage from '../page/HomePage';
import { connect } from "react-redux";
import {
createReactNavigationReduxMiddleware,
createReduxContainer
}
from "react-navigation-redux-helpers"
export const rootCom = 'HomePage'; // 設(shè)置根路由
const MainNavigator = createStackNavigator({
HomePage: {
screen: HomePage,
},
}, {
initialRouteName: rootCom
});
export const RootNavigator = createAppContainer(MainNavigator);
/**
* 1. 初始化 react-navigation 與 redux 的中間件
* 該方法的一個(gè)很大的作用是為 reduxifyNavigator 的 key 設(shè)置 actionSubscribers (行為訂閱者)
*/
export const middleware = createReactNavigationReduxMiddleware(
state => state.nav,
);
/**
* 2. 將導(dǎo)航器傳遞給 reduxifyNavigator 函數(shù)
* 并返回一個(gè)將 navigation state 和 dispatch 函數(shù)作為 props 的新組件
* 注意: 要在 createReactNavigationReduxMiddleware 之后執(zhí)行
*/
const AppWithNavigationState = createReduxContainer(RootNavigator);
/**
* State 和 Props 的映射關(guān)系
*/
const mapStateToProps = state => ({
state: state.nav
})
/**
* 連接 React 組件 與 Redux store
*/
export default connect(mapStateToProps)(AppWithNavigationState);
2. 配置 reducer
import {combineReducers} from 'redux';
import {createNavigationReducer} from 'react-navigation-redux-helpers'
import {RootNavigator} from '../navigator/AppNavigators';
import theme from './theme';
import popular from './popular';
// 1. 創(chuàng)建自己的 navigation reducer
const navReducer = createNavigationReducer(RootNavigator)
// 2. 合并 reducer
const index = combineReducers({
nav: navReducer,
themeReducer: theme, // 子 reducer
popularReducer: popular,// 子 reducer
})
export default index;
3. 使用 navigator
import {Provider} from 'react-redux';
import store from './js/store';
import AppNavigators from './js/navigator/AppNavigators';
export default class App extends Component {
render() {
return (
<Provider store={store}>
<AppNavigators />
</Provider>
)
}
}
至此, Redux 的基本使用都已經(jīng)介紹完了.
Tips
- Redux 應(yīng)用只有一個(gè) store, 當(dāng)需要拆分?jǐn)?shù)據(jù), 處理邏輯時(shí), 應(yīng)該使用 reducer 組合.
- redux 有一個(gè)特點(diǎn), 狀態(tài)共享, 所有的狀態(tài)都放在一個(gè) store 中, 任何 component 都可以訂閱 store 中的 state 數(shù)據(jù).
- 并不是所有的 state 都適合放在 store 中, 這樣會(huì)使 store 越來越大. 如果某個(gè) state 只被一個(gè)組件使用, 不存在狀態(tài)共享, 可以不放在 store 中.
- 如果你的項(xiàng)目不追求極致的條理, 可以不使用 Redux, 就好像 iOS 中再大的項(xiàng)目, MVC 這種架構(gòu)都是可以應(yīng)對(duì), 并且有針對(duì)其架構(gòu)的方案, 而不是使用 MVVM, 新的架構(gòu)是有學(xué)習(xí)成本的.
- 具體適不適合你自己的項(xiàng)目, 自己掂量.
enjoy :).