想着對 React 生態更深刻的學習,何況 React 源碼內容也很少的狀況,來讀一下他的源碼內容。 目前 Redux 已是 TypeScript 版本的源碼,我正在閱讀的則是
4.0.4
版本代碼。typescript
很顯然,能夠看到這和 React 倉庫同樣,一樣使用了 rollup 打包工具,也驗證了 rollup 在開發庫類倉庫的優點,畢竟大廠都在用嘛。json
咱們先來看到 package.json
:redux
{ "name": "redux", "version": "4.0.4", "main": "lib/redux.js", "unpkg": "dist/redux.js", "module": "es/redux.js", "types": "types/index.d.ts", } 複製代碼
我省略了大部份內容,能夠看到開發團隊針對不一樣使用場景,對外提供包對應的路徑,好比使用 TypeScript 時,對應的則是 types/index.d.ts
,或者使用 ES6 模塊化引入時則是 es/redux.js
。值得咱們學習。api
接下來,咱們看到源碼部分,整個源碼學習內容,將圍繞着 src
進行, utils
下的內容即一些簡單的工具, types
下則是一些類型的定義。數組
示例代碼中我會移除並省略掉一些邏輯代碼和 TypeScript 相關的函數重載代碼,以及一些我認爲不那麼重要的代碼,即只展示我想要說明的內容。markdown
那麼咱們先來看到 createStore
函數吧。app
export default function createStore< S, A extends Action, Ext = {}, StateExt = never >( reducer: Reducer<S, A>, preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>, enhancer?: StoreEnhancer<Ext, StateExt> ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext { // 接受的 reducer let currentReducer = reducer // 預加載 state let currentState = preloadedState as S // 訂閱隊列 let currentListeners: (() => void)[] | null = [] // 下一個訂閱隊列,看起來更像是訂閱隊列的副本,下面我會來介紹他和訂閱隊列的不解之謎 let nextListeners = currentListeners // 檢查並建立這個副本的函數 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { // 拷貝到一個新的數組引用,能夠理解爲 [...currentListeners] 使得 nextListeners !== currentListeners nextListeners = currentListeners.slice() } } // 得到 state function getState(): S { return currentState as S } // 訂閱函數 function subscribe(listener: () => void) { ensureCanMutateNextListeners() // 操做訂閱隊列副本,與 currentListeners 之間不受影響 nextListeners.push(listener) // 返回取消訂閱函數 return function unsubscribe() { ensureCanMutateNextListeners() // 一樣的效果 const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) currentListeners = null } // 看到這裏你是否是會想,建立這個副本的目的是什麼? // 官方給到的解釋:This prevents any bugs around consumers calling subscribe/unsubscribe in the middle of a dispatch. } // dispatch 函數 function dispatch(action: A) { currentState = currentReducer(currentState, action) // 遍歷通知訂閱隊列,將 currentListeners 賦值爲「最新」的隊列 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action } // 替換 reducer 函數 function replaceReducer<NewState, NewActions extends A>( nextReducer: Reducer<NewState, NewActions> ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext { // 更新 reducer ;((currentReducer as unknown) as Reducer< NewState, NewActions >) = nextReducer // 同初始化功能一致,即生成 currentState ,下面代碼中會說起 dispatch({ type: ActionTypes.REPLACE } as A) return (store as unknown) as Store< ExtendState<NewState, StateExt>, NewActions, StateExt, Ext > & Ext } // 初始化,生成 currentState ,即調用 getState 函數返回的內容, ActionTypes.INIT 便可以理解爲私有類型,不會影響到你本身寫的 reducer 畢竟裏面包含了隨機數... dispatch({ type: ActionTypes.INIT } as A) // 暴露的 store 對象 const store = ({ dispatch: dispatch as Dispatch<A>, subscribe, getState, replaceReducer, // 觀察者方法這裏不作解讀,能夠理解爲對 subscribe 的封裝 [?observable]: observable } as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext return store } 複製代碼
那麼到此,咱們看到了整個 createStore
函數的實現過程以及返回的內容。模塊化
這裏我來簡單的代過 compose
函數,也是業界的經常使用實現,不是很明白其功能的同窗能夠簡單看到這段代碼便可:函數
(funcs as : Function[]).reduce((a, b) => (...args: any) => a(b(...args))) 複製代碼
接下來,咱們來看看 combineReducers
的實現,你能夠把這個函數理解爲對 reducer
的合併,固然函數名就是這個意思。工具
export default function combineReducers(reducers: ReducersMapObject) { const reducerKeys = Object.keys(reducers) // 用於 combination 函數中使用所需,收集的有效 reducers const finalReducers: ReducersMapObject = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } // 收集的 keys const finalReducerKeys = Object.keys(finalReducers) return function combination( state: StateFromReducersMapObject<typeof reducers> = {}, action: AnyAction ) { let hasChanged = false // 新的 state const nextState: StateFromReducersMapObject<typeof reducers> = {} // 遍歷 keys for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] // 從遍歷中更新 state const nextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length // 返回 state 或新的 state return hasChanged ? nextState : state } } 複製代碼
在咱們看完 reducer
的組合工具函數後,咱們來看到綁定執行上下文的語法糖函數 bindActionCreators
。
實現代碼不多,即綁定執行上下文環境 this
。具體使用場景,能夠看到官網 API 文檔 bindActionCreators(actionCreators, dispatch) ,那麼直接看到源碼:
function bindActionCreator<A extends AnyAction = AnyAction>( actionCreator: ActionCreator<A>, dispatch: Dispatch ) { return function(this: any, ...args: any[]) { return dispatch(actionCreator.apply(this, args)) } } export default function bindActionCreators( actionCreators: ActionCreator<any> | ActionCreatorsMapObject, dispatch: Dispatch ) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } const boundActionCreators: ActionCreatorsMapObject = {} for (const key in actionCreators) { const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators } 複製代碼
最後,咱們來看到中間件函數 applyMiddleware
。
export default function applyMiddleware( ...middlewares: Middleware[] ): StoreEnhancer<any> { return (createStore: StoreCreator) => <S, A extends AnyAction>( reducer: Reducer<S, A>, ...args: any[] ) => { const store = createStore(reducer, ...args) let dispatch: Dispatch = () => { throw new Error('...') } const middlewareAPI: MiddlewareAPI = { getState: store.getState, dispatch: (action, ...args) => dispatch(action, ...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 對 store.dispatch 進行改寫,將其包裝在中間件的最後一層,以實現中間件的功能 dispatch = compose<typeof dispatch>(...chain)(store.dispatch) return { ...store, dispatch } } } 複製代碼
OK, Redux 源碼學習完了,是否是沒看夠,確實!
這纔是精妙的庫設計,簡潔的代碼實現強大的功能,那麼此次沒過癮不要緊,下次咱們來品一品 Redux 是如何和 React 結合在一塊兒的吧。