寫在開始
本篇主要結(jié)合react-native 使用redux的過程,說明使用redux的方法和原理,揭秘Redux單向數(shù)據(jù)流更新的機制原理甥捺,適合有一定Redux基礎(chǔ)的同學(xué)冠胯。
Redux 工作圖
![](https://cl.ly/022x3y2S1M32/Image%202017-04-13%20at%2001.14.19.png)
Redux原理圖
![](https://cl.ly/3Y1Q0z2n0L0F/Image%202017-04-13%20at%2001.16.53.png)
上圖是redux的工作原理圖,具體每一步驟的實現(xiàn)請見下面的詳細(xì)分析。
Redux需要懂的七大元素
- combineReducers()
- createStore()
- <Provider/>
- connect()
- mapStateToProps()
- mapDispatchToProps()
- action
一. combineReducers
將應(yīng)用整體state
劃分為不同的reducer
揖庄,最終合并為rootReducer
===>combineReducers()
1.1reducer
reducer
就是一個方法肮街,接收舊的 state
和當(dāng)前操作 action
先慷,返回新的 state
饮笛。需要注意的是reducer
是純函數(shù),永遠(yuǎn)不要在reducer
里做這些操作:
- 修改傳入?yún)?shù)熟掂;
- 執(zhí)行有副作用的操作缎浇,如 API 請求和路由跳轉(zhuǎn);
- 調(diào)用非純函數(shù)赴肚,如
Date.now()
或Math.random()
素跺。
1.2combineReducers()
用法
const rootReducer = combineReducers({
localCounter:localCounter,
serverCounter:serverCounter
})
1.3combineReducers()
作用
- 將子reducer用key-value的形式組成rootReducer,value是該子reducer的實現(xiàn)方法誉券。
- 返回值:一個總reducer指厌,內(nèi)部包含所有子reducer
1.4combineReducers()
關(guān)鍵源碼
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
...
if (typeof reducers[key] === 'function') {
//reduer數(shù)組
finalReducers[key] = reducers[key];
}
}
//reducer的key
var finalReducerKeys = Object.keys(finalReducers);
...
//返回一個function,該方法接收state和action作為參數(shù)踊跟,其實返回值就是rootReducer
//遍歷reducers數(shù)組踩验,將action傳入每個reducer方法中得到的新狀態(tài),與舊狀態(tài)對比是否變化商玫,若變化則返回新狀態(tài)箕憾,若沒有變化則返回舊狀態(tài)
return function combination() {
...
var hasChanged = false;
var nextState = {};
//遍歷reducer數(shù)組,執(zhí)行每個reducer的方法
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i];
var reducer = finalReducers[key];
var previousStateForKey = state[key];
//傳入舊state拳昌,action袭异,得到新state
var nextStateForKey = reducer(previousStateForKey, action);
...
nextState[key] = nextStateForKey;
//判斷狀態(tài)是否發(fā)生了改變
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
//返回值處理后的state
return hasChanged ? nextState : state;
};
}
二. createStore
然后根據(jù) rootReducer
創(chuàng)建store
===>createStore()
2.1 store
是redux的核心,存儲APP的所有狀態(tài)炬藤,只能有一個御铃。改變狀態(tài)的唯一方法是調(diào)用store.dispatch方法
2.2 createStore()
作用
- 兩種創(chuàng)建方式:
createStore(rootReducer,initialState);
createStore(rootReducer,initialState,applyMiddleware(thunkMiddleware));
A thunk is a function that wraps an expression to delay its evaluation.簡單來說一個 thunk 就是一個封裝表達(dá)式的函數(shù),封裝的目的是延遲執(zhí)行表達(dá)式
可以使用第三方庫來增強store沈矿,通常會使用redux-thunk庫來支持異步方法的dispatch上真。thunk最終起的作用,對dispatch調(diào)用的action進行檢查羹膳,如果action在第一次調(diào)用之后返回的是function睡互,則將(dispatch, getState)作為參數(shù)注入到action返回的方法中,執(zhí)行異步邏輯(相當(dāng)于開始一個新的action)陵像,若有返回對象則進行分發(fā)湃缎。
- 返回值
{Store} A Redux store that lets you read the state, dispatch actions
and subscribe to changes.
- (1)
dispatch(action)
: 用于action
的分發(fā),改變store
里面的state
- (2)
subscribe(listener)
: 注冊listener
蠢壹,store
里面state
發(fā)生改變后,執(zhí)行該listener
九巡。返回unsubscrib()
方法图贸,用于注銷當(dāng)前listener
。Redux采用了觀察者模式,store
內(nèi)部維護listener數(shù)組疏日,用于存儲所有通過store.subscribe
注冊的listener
- (3)
getState()
: 讀取store
里面的state
- (4)
replaceReducer()
: 替換reducer
偿洁,改變state
修改的邏輯
2.3 源碼分析
function createStore(reducer, preloadedState, enhancer) {
...
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
//返回增強store
return enhancer(createStore)(reducer, preloadedState);
}
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
/**
* 返回當(dāng)前狀態(tài)
*/
function getState() {
return currentState;
}
/**
* 注冊`listener`,維護一個listener的數(shù)組
* `store`里面`state`發(fā)生改變后沟优,執(zhí)行該`listener`
* 觀察者模式實現(xiàn)的關(guān)鍵
*/
function subscribe(listener) {
...
nextListeners.push(listener);
//返回一個注銷listener的方法
return function unsubscribe() {
...
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*/
var currentReducer = reducer;
function dispatch(action) {
//類型校驗...
try {
isDispatching = true;
//執(zhí)行rootReducer涕滋,得到新的state
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
//循環(huán)遍歷,執(zhí)行l(wèi)istener挠阁,通知數(shù)據(jù)改變了宾肺,listeners具體是什么?看容器組件
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*/
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
//返回值
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[_symbolObservable2['default']] = observable, _ref2;
}
三. Provider
將store
傳遞給應(yīng)用中的View
===> <Provider/>
3.1 <Provider/>
是redux提供的組件
<Provider store={store}>
<APP />
</Provider>
3.2 作用:將store傳遞給其子組件
將store設(shè)置到子組件的context中侵俗,這樣應(yīng)用的所有子組件就默認(rèn)都拿到了store
3.3 源碼
var Provider = function (_Component) {
_inherits(Provider, _Component);
//用于指定子組件可直接訪問的上下文數(shù)據(jù)锨用,所以子組件可以直接訪問store了
Provider.prototype.getChildContext = function getChildContext() {
return { store: this.store, storeSubscription: null };
};
function Provider(props, context) {
_classCallCheck(this, Provider);
var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
_this.store = props.store;
return _this;
}
Provider.prototype.render = function render() {
return _react.Children.only(this.props.children);
};
return Provider;
}(_react.Component);
exports.default = Provider;
四. connect
如何將react中的UI組件與redux的狀態(tài)、事件關(guān)聯(lián)起來====>connect()
方法
4.0 UI組件和容器組件
React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container component)隘谣。
UI組件:
①只負(fù)責(zé) UI 的呈現(xiàn)增拥,不帶有任何業(yè)務(wù)邏輯
②沒有狀態(tài)(即不使用this.state這個變量)
③所有數(shù)據(jù)都由參數(shù)(this.props)提供
④不使用任何 Redux 的 API
容器組件:
①負(fù)責(zé)管理數(shù)據(jù)和業(yè)務(wù)邏輯,不負(fù)責(zé) UI 的呈現(xiàn)
②帶有內(nèi)部狀態(tài)
③使用 Redux 的 API
4.1connect()
生成容器組件
- 通過<Provider/>傳遞store是給容器redux的容器組件
用于從 UI 組件生成容器組件寻歧。connect的意思掌栅,就是將這兩種組件連起來。
connect方法接受兩個參數(shù):mapStateToProps和mapDispatchToProps码泛。它們定義了 UI 組件的業(yè)務(wù)邏輯
let ConnectCounter = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
4.2 作用
connect
是一個高階函數(shù)猾封,首先傳入mapStateToProps
、mapDispatchToProps
弟晚,然后返回一個生產(chǎn)Component
的函數(shù)wrapWithConnect()
忘衍,然后再將真正的Component
作為參數(shù)傳入wrapWithConnect(MyComponent)
,這樣就生產(chǎn)出一個經(jīng)過包裹的Connect組件(也就是容器組件)卿城。
容器組件具有如下特點:
- (1)通過this.context獲取祖先Component的store枚钓,也就是通過<Provider/>傳遞過來的store。
- (2)props包括stateProps瑟押、dispatchProps搀捷、parentProps,合并在一起得到nextState,作為props傳給真正的Component多望,這樣在真正組件中就能通過this.props獲取到各種數(shù)據(jù)和方法嫩舟。
- (3)componentDidMount調(diào)用store.subscribe(listener)注冊監(jiān)聽方法,對store的變化進行訂閱怀偷,當(dāng)store變化的時候家厌,更新渲染view。
- (4)componentWillUnmount時注銷訂閱
4.3源碼分析
注意訂閱的實現(xiàn)
var Connect = function (_Component) {
_inherits(Connect, _Component);
/*
* 構(gòu)造函數(shù)中椎工,構(gòu)造一個訂閱對象饭于,屬性有this.store,方法this.onStateChange.bind(this)
*/
function Connect(props, context) {
...
//獲取store蜀踏。
//從父組件或context中獲取store。這里使用的是從context中獲取
//storeKey = _ref$storeKey === undefined ? 'store' : _ref$storeKey,
_this.store = props[storeKey] || context[storeKey];
...
//初始化訂閱邏輯
_this.initSubscription();
return _this;
}
//初始化訂閱方法
Connect.prototype.initSubscription = function initSubscription() {
if (!shouldHandleStateChanges) return;
var parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey];
//wym: 調(diào)用的是Subscription.js中方法,向store內(nèi)部注冊一個listener---this.onStateChange.bind(this)
this.subscription = new _Subscription2.default(this.store, parentSub, this.onStateChange.bind(this));
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription);
};
//當(dāng)數(shù)據(jù)狀態(tài)發(fā)生改變時
Connect.prototype.onStateChange = function onStateChange() {
this.selector.run(this.props);
if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs();
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate;
//設(shè)置state掰吕,view會自動重新渲染
this.setState(dummyState);
}
};
Connect.prototype.getChildContext = function getChildContext() {
...
};
/*
* 組件狀態(tài)完成時果覆,向store注冊監(jiān)聽方法
*/
Connect.prototype.componentDidMount = function componentDidMount() {
if (!shouldHandleStateChanges) return;
//實際調(diào)用this.store.subscribe(this.onStateChange);
//向store注冊監(jiān)聽方法
this.subscription.trySubscribe();
this.selector.run(this.props);
if (this.selector.shouldComponentUpdate) this.forceUpdate();
};
Connect.prototype.componentWillUnmount = function componentWillUnmount() {
//注銷訂閱
if (this.subscription) this.subscription.tryUnsubscribe();
this.subscription = null;
...
};
Connect.prototype.render = function render() {
var selector = this.selector;
selector.shouldComponentUpdate = false;
if (selector.error) {
throw selector.error;
} else {
return (0, _react.createElement)(WrappedComponent, this.addExtraProps(selector.props));
}
};
return Connect;
}(_react.Component);
五.mapStateToProps()
建立一個從(外部的)state對象到(UI 組件的)props對象的映射關(guān)系。mapStateToProps會訂閱 Store殖熟,每當(dāng)state更新的時候局待,就會自動執(zhí)行,重新計算 UI 組件的參數(shù)菱属,從而觸發(fā) UI 組件的重新渲染
六. mapDispatchToProps
- 用來建立 UI 組件的方法到store.dispatch方法的映射钳榨,它定義了哪些用戶的操作應(yīng)該當(dāng)作 Action,傳給 Store照皆。
- 它可以是一個函數(shù)重绷,也可以是一個對象。(只是不同的寫法膜毁,作用一樣)
七. Action
Action 是把數(shù)據(jù)從應(yīng)用傳到 store 的有效載荷昭卓。它是 store 數(shù)據(jù)的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store瘟滨。
分為:
- 同步 action 候醒,返回的是一個對象,要求是純凈的函數(shù)杂瘸。
純凈:沒有特殊情況倒淫、沒有副作用,沒有 API 請求败玉、沒有變量修改敌土,單純執(zhí)行計算。
- 異步action运翼,返回的是一個方法返干,這個函數(shù)會被Redux Thunk middleware執(zhí)行。這個函數(shù)并不需要保持純凈血淌;它還可以帶有副作用矩欠,包括執(zhí)行異步 API 請求。
Redux中的觀察者模式
redux之所以能夠當(dāng)state變化后悠夯,更新綁定的視圖癌淮,是因為內(nèi)部實現(xiàn)的觀察者模式
觀察者模式的實現(xiàn)
![](https://cl.ly/2F1Q442H3l2k/Image%202017-04-14%20at%2002.14.58.png)
1. store提供了注冊監(jiān)聽的方法===>subscribe(listener)
- store內(nèi)部維護listener數(shù)組,用于存儲所有通過store.subscrib注冊的listener沦补,store里面state發(fā)生改變(即為調(diào)用
store.dispatch()
)后乳蓄,依次執(zhí)行數(shù)組中的listener
。 - store.subscrib返回unsubscrib方法夕膀,用于注銷當(dāng)前l(fā)istener虚倒。
調(diào)用store.dispatch()的時候做了兩件事:
(1)更新當(dāng)前state: currentState = currentReducer(currentState, action);
(2)依次執(zhí)行數(shù)組中的listener匣摘。
2. Connect組件中,向store中注冊監(jiān)聽方法
- ①構(gòu)造方法中:初始化訂閱邏輯裹刮,將listener:this.onStateChange.bind(this)傳遞給Subscription.js。
- ②componentDidMount方法中庞瘸,調(diào)用store.subscribe(listener)注冊監(jiān)聽方法:
onStateChange()
捧弃。
onStateChange方法中,當(dāng)判斷需要更新數(shù)據(jù)時擦囊,調(diào)用的是this.setState(state);
===》根據(jù)react機制违霞,則會重新渲染view
- ③在componentWillUnmount方法中,注銷訂閱
Redux原理圖
根據(jù)以上的源碼分析瞬场,redux的工作流可用下圖進行概括买鸽。
![](https://cl.ly/3Y1Q0z2n0L0F/Image%202017-04-13%20at%2001.16.53.png)
redux創(chuàng)建過程概括
- 將一個APP的狀態(tài)分解成不同的reducer,最后創(chuàng)建store(有整個數(shù)據(jù)state贯被,有分發(fā)action的方法眼五,有注冊listener的方法)
- 將store通過<Provider/>組件傳遞給容器組件
- 容器組件通過UI組件,mapStateToProps, mapDispatchToProps通過connect()轉(zhuǎn)化而來
- 將UI交互事件彤灶,外部輸入事件寫成action看幼,用來觸發(fā)reducer
redux更新數(shù)據(jù)過程概括
- 在UI組件上有交互操作,或外部輸入(網(wǎng)絡(luò)請求)時幌陕,====>寫成Action
- store.dispatch(action)诵姜,結(jié)果:
(1)合成新的state,新的state通過mapStateToProps()傳遞給UI組件搏熄,
(2)執(zhí)行通過store.subscribe()注冊的listener - listener具體邏輯:調(diào)用setState()設(shè)置信息數(shù)值棚唆,觸發(fā)View的自動render