react在作大型項目的時候,前端的數據通常會愈來愈複雜,狀態的變化難以跟蹤、沒法預測,而redux能夠很好的結合react使用,保證數據的單向流動,能夠很好的管理整個項目的狀態,可是具體來講,下面是redux的一個核心流程圖:前端
即整個項目的數據存儲在Store中,每一個狀態下Store會生成一個state,一個state對應着一個view,而用戶只能接觸到view,不能接觸到store,那咱們怎麼才能讓store中的數據發生改變呢? 因此,必需要經過view來間接改變,即用戶點擊,產生action,不能點擊一次,建立一個action,因此須要一個action creator,而後將這個action經過dispatch函數送到store中,這個過程當中,可使用一些中間件來處理一些異步操做,而後將數據交給store,store拿到數據以後,經過reducer來根據不一樣的action的type來處理數據,返回一個新的state,經過新的state,就能夠再產生一個新的view了。 而且能夠看到 store、view、action這樣的一個單項數據流。 react
爲了更好地理解redux,咱們能夠讀一下redux的源碼。 webpack
首先,咱們將redux源碼獲得,總體目錄以下:git
而redux源碼的核心固然是處在src中的,dist、es、lib都不是最重要的,因此,咱們展開src,能夠看到下面的目錄:github
下面主要說一下總體:web
import createStore from './createStore' import combineReducers from './combineReducers' import bindActionCreators from './bindActionCreators' import applyMiddleware from './applyMiddleware' import compose from './compose' import warning from './utils/warning' /* * This is a dummy function to check if the function name has been altered by minification. * If the function has been minified and NODE_ENV !== 'production', warn the user. */ function isCrushed() {} if ( process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed' ) { warning( 'You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.' ) } export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose }
咱們能夠看到: 在index.js中,主要是從主流程文件、幾個輔助api文件以及日誌打印文件中獲取了接口,而後中間是一些環境方面的警告,能夠忽略,最後就經過這個index文件導出了全部咱們在redux使用的過程當中所須要的api,因此入口文件index.js就是起了一個橋樑了做用,很是簡單,可是很重要。編程
createStore.js主要是用於生成store的,咱們還能夠從最後暴露的對象看出其暴露了幾個方法:redux
return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
也就是說,這個主流程文件整個就是在定義了這麼幾個方法,下面看卡源碼(被精簡了,只留下重要部分):api
// 這個action必定是會被最早觸發的,從redux-devtools就能夠看得出來。 export const ActionTypes = { INIT: '@@redux/INIT' } // 接受三個參數 // 第一個參數是必須的,reducer,用來根據給定的狀態和action來返回下一個狀態樹,進一步致使頁面發生變化。 // 第二個參數是一個狀態,這個不是必須的,大部分時候不須要。 // 第三個參數是enhancer,一般是一箇中間件,好比使用redux-devtools這個中間件。 export default function createStore(reducer, preloadedState, enhancer) { // 當前的reducer let currentReducer = reducer // 當前的狀態。 let currentState = preloadedState // 能夠看出,這個是一個訂閱者,state有變化,須要告訴這些Listeners。 let currentListeners = [] // 後續的listeners,是不斷更新的。 let nextListeners = currentListeners // 是否dispatch。 let isDispatching = false function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } // 獲取當前的state樹 function getState() { return currentState } /** * @param {Function} listener A callback to be invoked on every dispatch. * @returns {Function} A function to remove this change listener. */ // 接收的是一個函數做爲參數, 這個函數會在每一次dispatch的時候被調用。 function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) // 取消訂閱 return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } } // 惟一觸發state改變的方式。 dispatch a action. // 這裏是dispatch的一個基本的實現,只能提供一個普通的對象。 若是你但願dispatch一個Promise、thunk、obserbable等,你須要包裝你的store // 建立函數進入一個相應的中間件,如 redux-thunk。 // 這個action必定要包含type類型。最後也會返回這個action function dispatch(action) { // 若是正在dispatch,則拋出錯誤。 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') }
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}數組
// dispatch的過程當中,每個listener都會被調用。 由於在subscribe中傳入的參數就是一個listener函數。 const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action } // 替換一個新的reducer function replaceReducer(nextReducer) { currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) } // observable函數 function observable() { const outerSubscribe = subscribe return { subscribe(observer) { if (typeof observer !== 'object') { throw new TypeError('Expected the observer to be an object.') } function observeState() { if (observer.next) { observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } } } // store被建立,就會有一個INIT action被處罰,因此每一個reducer就會返回他們的初始值了。 dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
getState方法很是簡單,就是獲取當前的store中的state,不用過多贅述。
replaceReducer方法也很簡單, 就是簡單的替換reducer。
其中,subscribe用於註冊監聽事件,而後返回取消訂閱的函數,把全部的訂閱事件都放在了一個訂閱數組裏,只要維護這個數組就行了,subscribe的做用就是這麼簡單。
每次dispatch的時候就會依次調用數組中的監聽事件。
store.subscribe()方法總結:
入參函數放入監聽隊列
返回取消訂閱函數
再看看dispatch方法,dispatch是觸發state改變的惟一方式,最爲核心的就是下面的這段代碼了:
try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }
這段代碼中,首先,將isDispatching設置爲了true,而後就調用currentReducer返回了一個新的currentState, 這樣就成功了改變了當前的狀態,最後, 改變了狀態以後,就開始把subscribe函數中註冊的事件開始以此執行。OK! 到這裏,dispatch方法就比較清楚了。
因此,這裏對dispatch方法作一個總結:
ok! 至此,主流程文件就已經分析完了,總體仍是比較簡單的,比較重要的一個函數就是dispatch,但也是很好理解的。
下面,主要講一講剩下的幾個輔助文件:
bindActionCreators把action creators轉成擁有同名keys的對象,使用dispatch把每一個action creator包裝起來,這樣能夠直接調用它們。
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
實際狀況用到的並很少,唯一的應用場景是當你須要把action creator往下傳到一個組件上,卻不想讓這個組件覺察到Redux的存在,並且不但願把Redux Store或dispatch傳給它。
這個文件中暴露的方法是咱們經常使用的,由於在寫reducer的時候,每每須要根據類別不一樣,寫多個reducer,可是根據createStore能夠知道,只有一個reducer能夠被傳入,因此這裏的combineReducers就是爲了將多個reducer合併成一個reducer的。具體源碼以下(通過精簡以後,就只剩下30多行了):
// 接受一個對象做爲參數,這個對象的值是須要被combine的不一樣的reducer函數。 // 返回一個函數, 這個函數就是一個大的reducer了。 export default function combineReducers(reducers) { // 獲取reducer的全部的keys數組。 const reducerKeys = Object.keys(reducers) // 最終的reducer對象。 const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] // 將全部的reducer從新放在finalReducers中,至關於淺拷貝。 if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } // 獲取最終的全部的reducers數組。 const finalReducerKeys = Object.keys(finalReducers) // 返回了一個函數,能夠看出這個函數和咱們一個一個定義的reducer函數是相似的,因此,這就是一個大的reducer函數。 return function combination(state = {}, action) { let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
因此這個combineReducers仍是很明確的,就是將全部的reducer組合成一個大的。
這個函數用於組合傳進來的一系列函數,在中間件的時候會用到,能夠看到,執行的最終結果就是把一系列函數串聯起來:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) }
在中間件的時候會用到這個函數。
這個函數用於 store 加強。
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { const store = createStore(reducer, preloadedState, enhancer) let dispatch = store.dispatch let chain = [] const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
用法大體以下:
const store = createStore(reducer,applyMiddleware(…middlewares)) or const store = createStore(reducer,{},applyMiddleware(…middlewares))
好比一個比較經常使用的redux-thunk中間件,源碼的關鍵代碼以下:
function createThunkMiddleware(extraArgument) { return function (_ref) { var dispatch = _ref.dispatch, getState = _ref.getState; return function (next) { return function (action) { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; }; }; }
做用的話能夠看到,這裏有個判斷:若是當前action是個函數的話,return一個action執行,參數有dispatch和getState,不然返回給下箇中間件。這種寫法就拓展了中間件的用法,讓action能夠支持函數傳遞。即若是action是一個函數,那麼咱們就能夠進一步來處理了,若是這個action是一個對象,說明就要直接又dispatch來觸發了,即這裏的action其實是在真正的dispatch以前所作的一些工做。
通常,咱們認爲redux屬於函數式編程,即函數是第一等公民、數據是不可變的(在reducer中,咱們但願每次返回一個新的state,而不是修改舊的state,而後返回,因此這裏強調的就是不可變的)、有肯定的輸入就有肯定的輸出。 總體來講,可能redux不是純純的函數式編程,可是也比較符合函數式編程的風格了。
以下:
const arr = [1, 2, 3]; arr.push(4); //這樣很差,看到這代碼我就方了,須要從上往下琢磨一下arr到底存的是啥 const newArr = [...arr, 4]; //這樣,arr不會被修改,很放心,要修改過的版本用newArr就行了
以下:
const me = {name: 'Morgan'}; me.skill = 'React'; //這樣很差,拿不許me裏是啥了 const newMe = {...me, skill: 'React'}; //這樣,me不會被修改