Redux 中文文檔 在此. 如果想找具體可操作的案例, 文檔里面都有. 文末有彩蛋.
為什么會(huì)有 Redux ?
在 iOS 中, 隨著項(xiàng)目迭代, 功能越來越復(fù)雜, 如果還是采用 MVC 架構(gòu), 由于 Controller
內(nèi)部的職責(zé)太多, 而導(dǎo)致代碼塊耦合嚴(yán)重, 不利于測(cè)試和維護(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è)場(chǎng)景的 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ù)測(cè)化的 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)用場(chǎ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)建的Redux
store
沒有使用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
開始dispatch
action
時(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 盡可能簡(jiǎn)單, 我們不能在這里面改變 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 :).