解讀redux工作原理
1. 前言
隨著WEB應用變得越來越復雜,再加上node前后端分離越來越流行,那么對數據流動的控制就顯得越發重要。redux是在flux的基礎上產生的,基本思想是保證數據的單向流動,同時便于控制、使用、測試。
redux不依賴于任意框架(庫),只要subscribe相應框架(庫)的內部方法,就可以使用該應用框架保證數據流動的一致性。
那么如何使用redux呢?下面一步步進行解析,并帶有源碼說明,不僅做到 知其然 ,還要做到 知其所以然 。
2. 主干邏輯介紹(createStore)
2.1 簡單demo入門
先來一個直觀的認識:
// 首先定義一個改變數據的plain函數,成為reducer
function count (state, action) {
var defaultState = {
year: 2015,
};
state = state || defaultState;
switch (action.type) {
case 'add':
return {
year: state.year + 1
};
case 'sub':
return {
year: state.year - 1
}
default :
return state;
}
}
// store的創建
var createStore = require('redux').createStore;
var store = createStore(count);
// store里面的數據發生改變時,觸發的回調函數
store.subscribe(function () {
console.log('the year is: ', store.getState().year);
});
// action: 觸發state改變的唯一方法(按照redux的設計思路)
var action1 = { type: 'add' };
var action2 = { type: 'add' };
var action3 = { type: 'sub' };
// 改變store里面的方法
store.dispatch(action1); // 'the year is: 2016
store.dispatch(action2); // 'the year is: 2017
store.dispatch(action3); // 'the year is: 2016 2.2 挖掘createStore實現
為了說明主要問題,僅列出其中的關鍵代碼,全部代碼,可以點擊 這里 閱讀。
a首先看createStore到底都返回的內容:
export default function createStore(reducer, initialState) {
...
return {
dispatch,
subscribe,
getState,
replaceReducer
}
} 每個屬性的含義是: - dispatch: 用于action的分發,改變store里面的state - subscribe: 注冊listener,store里面state發生改變后,執行該listener - getState: 讀取store里面的state - replaceReducer: 替換reducer,改變state修改的邏輯
b關鍵代碼解析
export default function createStore(reducer, initialState) {
// 這些都是閉包變量
var currentReducer = reducer
var currentState = initialState
var listeners = []
var isDispatching = false;
// 返回當前的state
function getState() {
return currentState
}
// 注冊listener,同時返回一個取消事件注冊的方法
function subscribe(listener) {
listeners.push(listener)
var isSubscribed = true
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
// 通過action該改變state,然后執行subscribe注冊的方法
function dispatch(action) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
listeners.slice().forEach(listener => listener())
return action
}
// 替換reducer,修改state變化的邏輯
function replaceReducer(nextReducer) {
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
// 初始化時,執行內部一個dispatch,得到初始state
dispatch({ type: ActionTypes.INIT })
} 如果還按照2.1的方式進行開發,那跟flux沒有什么大的區別,需要手動解決很多問題,那redux如何將整個流程模板化(Boilerplate)呢?
3. 保證store的唯一性
隨著應用越來越大,一方面,不能把所有的數據都放到一個reducer里面,另一方面,為每個reducer創建一個store,后續store的維護就顯得比較麻煩。如何將二者統一起來呢?
3.1 demo入手
通過combineReducers將多個reducer合并成一個rootReducer: // 創建兩個reducer: count year function count (state, action) { state = state || {count: 1} switch (action.type) { default: return state; } } function year (state, action) { state = state || {year: 2015} switch (action.type) { default: return state; } }
// 將多個reducer合并成一個
var combineReducers = require('./').combineReducers;
var rootReducer = combineReducers({
count: count,
year: year,
});
// 創建store,跟2.1沒有任何區別
var createStore = require('./').createStore;
var store = createStore(rootReducer);
var util = require('util');
console.log(util.inspect(store));
//輸出的結果,跟2.1的store在結構上不存在區別
// { dispatch: [Function: dispatch],
// subscribe: [Function: subscribe],
// getState: [Function: getState],
// replaceReducer: [Function: replaceReducer]
// } 3.2 源碼解析combineReducers
// 高階函數,最后返回一個reducer
export default function combineReducers(reducers) {
// 提出不合法的reducers, finalReducers就是一個閉包變量
var finalReducers = pick(reducers, (val) => typeof val === 'function')
// 將各個reducer的初始state均設置為undefined
var defaultState = mapValues(finalReducers, () => undefined)
// 一個總reducer,內部包含子reducer
return function combination(state = defaultState, action) {
var finalState = mapValues(finalReducers, (reducer, key) => {
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
return nextStateForKey
);
return hasChanged ? finalState : state
}
} 4. 自動實現dispatch
4.1 demo介紹
在2.1中,要執行state的改變,需要手動dispatch:
var action = { type: '***', payload: '***'};
dispatch(action); 手動dispatch就顯得啰嗦了,那么如何自動完成呢?
var bindActionCreators = require('redux').bindActionCreators;
// 可以在具體的應用框架隱式進行該過程(例如react-redux的connect組件中)
bindActionCreators(action) 4.2 源碼解析
// 隱式實現dispatch
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
return mapValues(actionCreators, actionCreator =>
bindAQctionCreator(actionCreator, dispatch)
)
} 5. 支持插件 - 對dispatch的改造
5.1 插件使用demo
一個action可以是同步的,也可能是異步的,這是兩種不同的情況, dispatch執行的時機是不一樣的:
// 同步的action creator, store可以默認實現dispatch
function add() {
return { tyle: 'add' }
}
dispatch(add());
// 異步的action creator,因為異步完成的時間不確定,只能手工dispatch
function fetchDataAsync() {
return function (dispatch) {
requst(url).end(function (err, res) {
if (err) return dispatch({ type: 'SET_ERR', payload: err});
if (res.status === 'success') {
dispatch({ type: 'FETCH_SUCCESS', payload: res.data });
}
})
}
} 下面的問題就變成了,如何根據實際情況實現不同的dispatch方法,也即是根據需要實現不同的moddleware:
// 普通的dispatch創建方法
var store = createStore(reducer, initialState);
console.log(store.dispatch);
// 定制化的dispatch
var applyMiddleware = require('redux').applyMiddleware;
// 實現action異步的middleware
var thunk = requre('redux-thunk');
var store = applyMiddleware([thunk])(createStore);
// 經過處理的dispatch方法
console.log(store.dispatch); 5.2 源碼解析
// next: 其實就是createStore
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
var store = next(reducer, initialState)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch // 實現新的dispatch方法
}
}
}
// 再看看redux-thunk的實現, next就是store里面的上一個dispatch
function thunkMiddleware({ dispatch, getState }) {
return function(next) {
return function(action) {
typeof action === 'function' ? action(dispatch, getState) : next(action);
}
}
return next => action =>
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
} 6. 與react框架的結合
6.1 基本使用
目前已經有現成的工具 react-redux 來實現二者的結合:
var rootReducers = combineReducers(reducers);
var store = createStore(rootReducers);
var Provider = require('react-redux').Provider;
// App 為上層的Component
class App extend React.Component{
render() {
return (
<Provier store={store}>
<Container />
</Provider>
);
}
}
// Container作用: 1. 獲取store中的數據; 2.將dispatch與actionCreator結合起來
var connect = require('react-redux').connect;
var actionCreators = require('...');
// MyComponent是與redux無關的組件
var MyComponent = require('...');
function select(state) {
return {
count: state.count
}
}
export default connect(select, actionCreators)(MyComponent) 6.2 Provider – 提供store
React通過Context屬性,可以將屬性(props)直接給子孫component,無須通過props層層傳遞, Provider僅僅起到獲得store,然后將其傳遞給子孫元素而已:
export default class Provider extends Component {
getChildContext() { // getChildContext: 將store傳遞給子孫component
return { store: this.store }
}
constructor(props, context) {
super(props, context)
this.store = props.store
}
componentWillReceiveProps(nextProps) {
const { store } = this
const { store: nextStore } = nextProps
if (store !== nextStore) {
warnAboutReceivingStore()
}
}
render() {
let { children } = this.props
return Children.only(children)
}
} 6.3 connect – 獲得store及dispatch(actionCreator)
connect是一個高階函數,首先傳入mapStateToProps、mapDispatchToProps,然后返回一個生產 Component 的函數(wrapWithConnect),然后再將真正的Component作為參數傳入wrapWithConnect(MyComponent),這樣就生產出一個經過包裹的Connect組件,該組件具有如下特點:
- 通過this.context獲取祖先Component的store
- props包括stateProps、dispatchProps、parentProps,合并在一起得到 nextState ,作為props傳給真正的Component
- componentDidMount時,添加事件this.store.subscribe(this.handleChange),實現頁面交互
- shouldComponentUpdate時判斷是否有避免進行渲染,提升頁面性能,并得到nextState
- componentWillUnmount時移除注冊的事件this.handleChange
- 在非生產環境下,帶有熱重載功能
主要的代碼邏輯:
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
// 從祖先Component處獲得store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = { storeState: null }
// 對stateProps、dispatchProps、parentProps進行合并
this.updateState()
}
shouldComponentUpdate(nextProps, nextState) {
// 進行判斷,當數據發生改變時,Component重新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 改變Component的state
this.store.subscribe(() = {
this.setState({
storeState: this.store.getState()
})
})
}
render() {
// 生成包裹組件Connect
return (
<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {
store: storeShape
}
return Connect;
}
} 7. redux與react-redux關系圖