歡迎訪問我的博客的其餘文章html
隨着WEB應用變得愈來愈複雜,再加上node先後端分離愈來愈流行,那麼對數據流動的控制就顯得愈加重要。redux是在flux的基礎上產生的,基本思想是保證數據的單向流動,同時便於控制、使用、測試。node
redux不依賴於任意框架(庫),只要subscribe相應框架(庫)的內部方法,就可使用該應用框架保證數據流動的一致性。react
那麼如何使用redux呢?下面一步步進行解析,並帶有源碼說明,不只作到知其然,還要作到知其因此然。git
先來一個直觀的認識:github
// 首先定義一個改變數據的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
爲了說明主要問題,僅列出其中的關鍵代碼,所有代碼,能夠點擊這裏閱讀。redux
a 首先看createStore到底都返回的內容:後端
export default function createStore(reducer, initialState) { ... return { dispatch, subscribe, getState, replaceReducer } }
每一個屬性的含義是:閉包
dispatch: 用於action的分發,改變store裏面的stateapp
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)呢?
隨着應用愈來愈大,一方面,不能把全部的數據都放到一個reducer裏面,另外一方面,爲每一個reducer建立一個store,後續store的維護就顯得比較麻煩。如何將兩者統一塊兒來呢?
經過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] // }
// 高階函數,最後返回一個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 }
在2.1中,要執行state的改變,須要手動dispatch:
var action = { type: '***', payload: '***'}; dispatch(action);
手動dispatch就顯得囉嗦了,那麼如何自動完成呢?
var bindActionCreators = require('redux').bindActionCreators; // 能夠在具體的應用框架隱式進行該過程(例如react-redux的connect組件中) bindActionCreators(action)
// 隱式實現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) ) }
一個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);
// 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); }
目前已經有現成的工具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)
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) } }
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; }
}