目錄javascript
Redux 源碼能夠在任意項目中的 node_modules
文件夾下的 redux
中找到。咱們閱讀學習中主要關注 src 便可。java
src 下主要分紅兩個部分, 一部分是 utils
工具庫, 一部分是 redux
邏輯代碼。node
Redux 自定義的工具庫react
下屬對應三個文件webpack
源碼以下:git
const randomString = () => Math.random() .toString(36) .substring(7) .split('') .join('.') const ActionTypes = { INIT: `@@redux/INIT${randomString()}`, REPLACE: `@@redux/REPLACE${randomString()}`, PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}` } export default ActionTypes
這個文件主要是用來對外暴露三個 action 類型,比較好理解。github
其中的 randomString
方法用來獲取指定長度的隨機字符串, 這裏有個不少同窗都會忽略掉的知識點, Number.prototype.toString 方法 接受一個可選參數 radix
,該參數表明數字的基數, 也就是咱們常數的二進制、八進制等, 默認爲 10, 範圍在 2~36之間。web
源碼以下:redux
export default function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto }
這個文件對外暴露一個用來判斷是否爲簡單對象的方法。segmentfault
簡單對象
凡不是new Object()或者字面量的方式構建出來的對象都不是簡單對象
就是該對象的 __proto__
等於 Object.prototype
舉🌰, 像 正則, 日期,類, 函數, 都不是簡單對象
具體的相關知識點,能夠去看下原型鏈的相關知識。
源碼以下:
export default function warning(message) { if (typeof console !== 'undefined' && typeof console.error === 'function') { console.error(message) } try { throw new Error(message) } catch (e) {} }
這個文件也是很簡單只是用來打印一下錯誤信息, 這裏對 console.error
加了層判斷處理, 用於處理兼容性問題。緣由是 ie8及其如下都是不支持 console
下屬文件以下:
咱們首先從入口文件開始閱讀:
源碼以下:
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' import __DO_NOT_USE__ActionTypes from './utils/actionTypes' 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 setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' + 'to ensure you have the correct code for your production build.' ) } export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes }
這裏的入口文件主要的工做是把其他的幾個文件作一個統一導出, 須要關注的地方有兩點
__DO_NOT_USE__ActionTypes
。 經過導入路徑能夠得知,引用自以前 untils
中的 actionTypes.js
中,平時工做中也用不到,官方文檔也未作解釋,暫且放下無論,之後再研究 。。。
函數 isCrushed
從字面理解,這是函數是用來判斷是不是碎的, 但函數體中卻什麼都沒有,非常奇怪,帶着這個疑問往下看這個 if
語句,在這裏給出了答案,
process.env.NODE_ENV !== 'production'
typeof isCrushed.name === 'string'
isCrushed.name !== 'isCrushed'
這三個條件從字面上也很是好理解。 當三個條件都不知足時 執行 warning
拋出異常信息。信息內容翻譯以下:
「您目前正在使用NODE_ENV =''production'以外的縮小代碼。」+
'這意味着你正在運行一個較慢的Redux開發版本。 '+
'你可使用loose-envify(https://github.com/zertosh/loose-envify)進行瀏覽器化'+
'或者爲Webpack定義插件(http://stackoverflow.com/questions/30030031)'+
'以確保您擁有正確的生產構建代碼。
可能有同窗有對於前兩個條件還好理解, 對第三個條件會感到有些疑惑, 以爲沒有必要。這裏瞭解過些打包原理的同窗應該對這個判斷仍是比較好理解的。
以 create-react-app
爲例: 項目代碼在生產環境下會對代碼內容進行一個深度壓縮,會將全部的變量名替換成 a
, b
, c
之類的字母, 因此當進行生成環境編譯後 函數 isCrushed
能夠就變成了 函數 i
。
這個函數的主要做用就是防止開發者在開發環境下對代碼進行壓縮, 影響調試
源碼以下:
import $$observable from 'symbol-observable' import ActionTypes from './utils/actionTypes' import isPlainObject from './utils/isPlainObject' export default function createStore(reducer, preloadedState, enhancer) { // 參數處理 if ( (typeof preloadedState === 'function' && typeof enhancer === 'function') || (typeof enhancer === 'function' && typeof arguments[3] === 'function') ) { throw new Error( 'It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function' ) } if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } // 定義變量 let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false // 定義方法 function ensureCanMutateNextListeners() { ... } function getState() { ... } function subscribe(listener) { ... } function dispatch(action) { ... } function replaceReducer(nextReducer) { ... } dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
函數 createStore
建立一個 Redux store 來以儲存應用中全部的 state。應用用應有且僅有一個 store。
createStore(reducer, [preloadedState], enhancer)
返回的接口分別是 dispathc
subscribe
getState
replaceReducer
和 [$$observable]
參數
reducer: function
接收兩個參數, 分別是當前的 state 樹和要處理的 action, 返回新的 state 樹。
[preloadedState]: any
初始時的 state, 若是隻有兩個參數,而且第二個參數是個函數時,將此參數賦值爲第三個參數, 並忽略此參數
因此咱們在使用時能夠不傳第二個參數,直接將第三個參數書寫到第二個參數的位置 一樣能夠正常使用
enhancer: func
enhaner 加強器 是一個組合 stroe creator 的高階函數,返回一個新的強化後的 store creator。 一般能夠理解爲中間件,同時它也容許你經過複合函數改變 store 接口。
注意: 當存在多個參數時,且從第三個參數開始類型同爲 函數, createStroe 將默認理解你想要使用多個 加強器, 這裏會拋出異常提示你須要將多個加強器合併成一個函數傳入。
常見的 enhancer
有 redux-thunk 以及 redux-sage。通常都會配合 applyMiddleware 一塊兒使用, 其做用就是將這些 enhancer 格式化成符合 redux 要求的 enhancer。
舉個🌰:
import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' import logger from 'redux-logger' import reducer from './reducers' const store = createStore(reducer, applyMiddleware(thunk)) export default store
變量
let currentState = preloadedState //從函數createStore第二個參數preloadedState得到 let currentReducer = reducer //從函數createStore第一個參數reducer得到 let currentListeners = [] //當前訂閱者列表 let nextListeners = currentListeners //新的訂閱者列表 let isDispatching = false
其中的 isDispatching
是做爲鎖來用的, 咱們都知道 redux 是一個統一的狀態管理容器。因此同一時間裏咱們必須保證只要一個action在執行
ensureCanMutateNextListeners
function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
確保 nextListeners 與 currentListeners 不存在同一個引用關係, 主要服務於 dispatch 與訂閱。
dispatch
function dispatch(action) { if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } 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() } return action }
這裏咱們能夠看到 dispatch 方法中作了三重判斷,
1. action 必須是個簡單函數; 2. action.type 存在; 3. 未鎖住狀態。
只有當三重判斷都經過時, 才往下執行 action 並進行上鎖
getState
function getState() { if (isDispatching) { throw new Error( 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.' ) } return currentState }
getState 很是簡單, 返回 currentState 便可。這個 currentState 在每次 dispatch 的時候都會進行更新, 同dipatch 同樣在進行 reducer 操做的時候, 是不能夠讀取到當前 state 裏的值的。
提問: 執行 createStore 函數生成 store, 可不能夠直接修改它的 state?
答案是: 能夠的, 但redux 不容許這麼去作, 由於這樣不會通知到訂閱者從新更新數據。
subscribe
function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } if (isDispatching) { throw new Error( 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } if (isDispatching) { throw new Error( 'You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
這個函數能夠給 store 的狀態添加訂閱監聽函數,一旦調用 dispatch
,全部的監聽函數就會執行;
nextListeners
就是儲存當前監聽函數的列表,調用 subscribe
,傳入一個函數做爲參數,那麼就會給 nextListeners
列表 push
這個函數;
同時調用 subscribe
函數會返回一個 unsubscribe
函數,用來解綁當前傳入的函數,同時在 subscribe
函數定義了一個 isSubscribed
標誌變量來判斷當前的訂閱是否已經被解綁,解綁的操做就是從 nextListeners
列表中刪除當前的監聽函數。
replaceReducer
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) }
這個函數能夠替換 store 當前的 reducer 函數,首先直接把 currentReducer = nextReducer
,直接替換;
而後 dispatch({ type: ActionTypes.INIT })
,用來初始化替換後 reducer 生成的初始化狀態而且賦予 store 的狀態;平時很難用到。
observable
/** * Interoperability point for observable/reactive libraries. * @returns {observable} A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */ function observable() { ... }
這個不是暴露給咱們開發者的, 不須要掌握。所以貼給註釋,就不貼詳細代碼, 關於 函數 observable
有興趣的能夠去做者的 gayhub(github)去學習一下: https://github.com/tc39/proposal-observable
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))) }
這個函數的意義就是將多個函數方法合併組成一個函數並返回。 注意這裏的組合是從右像左進行組合。
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
applyMiddleware 須要結合到 createStore 中的第三個參數 enhancer
加強器。經過對比咱們能夠得出 applyMiddleware 經過組合多箇中間件返回的一個 enhaner 。
能夠看一下前面 createStore 中 enhaner
的相關代碼
if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) }
這裏的 enhancer === applyMiddleware(...)
。。。( 讀不動了 ,😳懵逼中,,, 理解不了 🤣🤣 , 跳過 跳過 😂😂)
function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } } 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 的函數。
第一個參數類型應爲一個函數或者對象。 當爲對象時, 直接調用 bindActionCreator
方法進行返回。 當爲對象時, 對對象進行遍歷 將其中爲 類型爲 function 的 再次整合到一個對象中進行集中返回。
import ActionTypes from './utils/actionTypes' import warning from './utils/warning' import isPlainObject from './utils/isPlainObject' function getUndefinedStateErrorMessage(key, action) { ... } function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) { ... } function assertReducerShape(reducers) { ... } export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (process.env.NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let shapeAssertionError try { assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } return function combination(state = {}, action) { if (shapeAssertionError) { throw shapeAssertionError } if (process.env.NODE_ENV !== 'production') { const warningMessage = getUnexpectedStateShapeWarningMessage( state, finalReducers, action, unexpectedKeyCache ) if (warningMessage) { warning(warningMessage) } } 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) if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
這個js 主要用來將多個 reducer 合併成一個 進行統一返回, 工做中也比較常見。
工具方法:
assertReducerShape
function assertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const initialState = reducer(undefined, { type: ActionTypes.INIT }) if (typeof initialState === 'undefined') { throw new Error( `Reducer "${key}" returned undefined during initialization. ` + `If the state passed to the reducer is undefined, you must ` + `explicitly return the initial state. The initial state may ` + `not be undefined. If you don't want to set a value for this reducer, ` + `you can use null instead of undefined.` ) } if ( typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION() }) === 'undefined' ) { throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don't try to handle ${ ActionTypes.INIT } or other actions in "redux/*" ` + `namespace. They are considered private. Instead, you must return the ` + `current state for any unknown actions, unless it is undefined, ` + `in which case you must return the initial state, regardless of the ` + `action type. The initial state may not be undefined, but can be null.` ) } }) }
函數 assertReducerShape 將對傳入的 reducer 作了兩個判斷處理, 兩個判斷分別針對於 初始的 state 未定義, 與 傳入一個未知的 type 作異常處理。 經過這裏咱們就不難理解 爲何 reducer 中爲什麼必須返回一個初始的 state 與 在 switch 中的 default 返回 原有的 state。
getUnexpectedStateShapeWarningMessage
function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) { const reducerKeys = Object.keys(reducers) const argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer' if (reducerKeys.length === 0) { return ( 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.' ) } if (!isPlainObject(inputState)) { return ( `The ${argumentName} has unexpected type of "` + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + `". Expected argument to be an object with the following ` + `keys: "${reducerKeys.join('", "')}"` ) } const unexpectedKeys = Object.keys(inputState).filter( key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] ) unexpectedKeys.forEach(key => { unexpectedKeyCache[key] = true }) if (action && action.type === ActionTypes.REPLACE) return if (unexpectedKeys.length > 0) { return ( `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` + `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` + `Expected to find one of the known reducer keys instead: ` + `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.` ) } }
獲取意外狀態形狀警告消息, 用來定義返回一下異常信息的描述內容, 供 warning 調用, 條件有: 傳入的 reducer 不能爲空對象、state 必須爲一個簡單對象、意外的 reducers 緩存。
getUndefinedStateErrorMessage
function getUndefinedStateErrorMessage(key, action) { const actionType = action && action.type const actionDescription = (actionType && `action "${String(actionType)}"`) || 'an action' return ( `Given ${actionDescription}, reducer "${key}" returned undefined. ` + `To ignore an action, you must explicitly return the previous state. ` + `If you want this reducer to hold no value, you can return null instead of undefined.` ) }
獲取未定義的狀態錯誤消息
業務邏輯
assertReducerShape
檢測 finalReducers 是否都有默認返回值hasChanged
來表示 state 是否產生變化,遍歷reducers集合,將每一個reducer對應的原state傳入其中,得出其對應的新的state。緊接着後面對新的state作了一層未定義的校驗。 校驗後會與原 state 進行比對,發生變化則返回 nextState, 反之返回 原 state。到這整個源碼算是粗略的讀了一遍,雖然代碼量很少, 只有區區數百行, 可是很繞的, 到如今有些地方依舊不能理解。不過對自身的幫助仍是很大, 這期間也拜讀了很多大佬的源碼剖析。在這也很感謝 有那麼多的大佬分享本身的學習心得體會。其中特別是 wuming 大佬的 redux源碼剖析 寫的十分精彩,有須要的同窗能夠前往拜讀一下。 這份源碼閱讀還存在不少理解不足, 隨着後續的理解進一步深刻,也會再次更新的。