摘要:也可以看我的博客解析源碼解析源碼是狀態容器,提供可預測化的狀態管理。作為全家桶的一份子,可謂說也是名聲響響,在年學習想必沒有多少人沒聽過吧。
也可以看我的博客 - 解析 Redux 源碼
解析 Redux 源碼 TIPRedux 是 JavaScript 狀態容器,提供可預測化的狀態管理。
作為 React 全家桶的一份子,Redux 可謂說也是名聲響響,在 2016 年學習 JavaScript 想必沒有多少人沒聽過吧。
這里,本文不是來教大家如何使用 Redux 的 API 的,這一類的文章已經很多,對于 Redux 的介紹和學習可以點擊下列鏈接:
官方文檔
官方Github
中文文檔
redux 大法好 —— 入門實例 TodoList(本人的渣渣文章)
Redux 體小精悍(只有2kB)且沒有任何依賴,因此本文想通過閱讀 Redux 的源碼來學習 Redux 的使用以及思想。
源碼結構Redux 的源碼結構很簡單,我們可以直接看 src 目錄下的代碼:
.src ├── utils #工具函數 ├── applyMiddleware.js ├── bindActionCreators.js ├── combineReducers.js ├── compose.js ├── createStore.js └── index.js #入口 jsindex.js
這個是整個代碼的入口:
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" function isCrushed() {} if ( process.env.NODE_ENV !== "production" && typeof isCrushed.name === "string" && isCrushed.name !== "isCrushed" ) { warning( "。。。" ) } export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose }
這里的 isCrushed 函數主要是為了驗證在非生產環境下 Redux 是否被壓縮(因為如果被壓縮了那么 (isCrushed.name !== "isCrushed") 就是 true),如果被壓縮會給開發者一個 warn 提示)。
然后就是暴露 createStore combineReducers bindActionCreators applyMiddleware compose 這幾個接口給開發者使用,我們來逐一解析這幾個 API。
createStore.js這個是 Redux 最主要的一個 API 了,它創建一個 Redux store 來以存放應用中所有的 state,應用中應有且僅有一個 store。
import isPlainObject from "lodash/isPlainObject" import $$observable from "symbol-observable" // 私有 action export var ActionTypes = { INIT: "@@redux/INIT" } export default function createStore(reducer, preloadedState, enhancer) { // 判斷接受的參數個數,來指定 reducer 、 preloadedState 和 enhancer if (typeof preloadedState === "function" && typeof enhancer === "undefined") { enhancer = preloadedState preloadedState = undefined } // 如果 enhancer 存在并且適合合法的函數,那么調用 enhancer,并且終止當前函數執行 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.") } // 儲存當前的 currentReducer var currentReducer = reducer // 儲存當前的狀態 var currentState = preloadedState // 儲存當前的監聽函數列表 var currentListeners = [] // 儲存下一個監聽函數列表 var nextListeners = currentListeners var isDispatching = false // 這個函數可以根據當前監聽函數的列表生成新的下一個監聽函數列表引用 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } ... getState ... ... subscribe ... ... dispatch ... ... replaceReducer ... ... observable ... dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
首先定義了一個 ActionTypes 對象,它是一個 action,是一個 Redux 的私有 action,不允許外界觸發,用來初始化 Store 的狀態樹和改變 reducers 后初始化 Store 的狀態樹。
createStore然后著重來看 createStore 函數:
它可以接受三個參數,reducer、preloadedState、enhancer:
reducer:是一個函數,返回下一個狀態,接受兩個參數:當前狀態 和 觸發的 action;
preloadedState:初始狀態對象,可以很隨意指定,比如服務端渲染的初始狀態,但是如果使用 combineReducers 來生成 reducer,那必須保持狀態對象的 key 和 combineReducers 中的 key 相對應;
enhancer:store 的增強器函數,可以指定為 第三方的中間件,時間旅行,持久化 等等,但是這個函數只能用 Redux 提供的 applyMiddleware 函數來生成;
根據傳入參數的個數和類型,判斷 reducer 、 preloadedState 、 enhancer;
調用完函數它返回的接口是 dispatch subscribe getState replaceReducer 和 [$$observable]
這也是我們開發中主要使用的幾個接口。
如果 enhancer 參數傳入并且是個合法的函數,那么就是調用 enhancer 函數(傳入 createStore 來給它操作),enhancer 函數返回的也是一個函數,在這里傳入 reducer 和 preloadedState,并且返回函數調用結果,終止當前函數執行;
在 enhancer 函數里面是如何操作使用的可以看 applyMiddleware 部分;
function getState() { return currentState }
這個函數可以獲取當前的狀態,createStore 中的 currentState 儲存當前的狀態樹,這是一個閉包,這個參數會持久存在,并且所有的操作狀態都是改變這個引用,getState 函數返回當前的 currentState。
function subscribe(listener) { // 判斷傳入的參數是否為函數 if (typeof listener !== "function") { throw new Error("Expected listener to be a function.") } var isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
這個函數可以給 store 的狀態添加訂閱監聽函數,一旦調用 dispatch ,所有的監聽函數就會執行;
nextListeners 就是儲存當前監聽函數的列表,調用 subscribe,傳入一個函數作為參數,那么就會給 nextListeners 列表 push 這個函數;
同時調用 subscribe 函數會返回一個 unsubscribe 函數,用來解綁當前傳入的函數,同時在 subscribe 函數定義了一個 isSubscribed 標志變量來判斷當前的訂閱是否已經被解綁,解綁的操作就是從 nextListeners 列表中刪除當前的監聽函數。
function dispatch(action) { if (!isPlainObject(action)) { throw new Error( "Actions must be plain objects. " + "Use custom middleware for async actions." ) } // 判斷 action 是否有 type{必須} 屬性 if (typeof action.type === "undefined") { throw new Error( "Actions may not have an undefined "type" property. " + "Have you misspelled a constant?" ) } // 如果正在 dispatch 則拋出錯誤 if (isDispatching) { throw new Error("Reducers may not dispatch actions.") } // 對拋出 error 的兼容,但是無論如何都會繼續執行 isDispatching = false 的操作 try { isDispatching = true // 使用 currentReducer 來操作傳入 當前狀態和action,放回處理后的狀態 currentState = currentReducer(currentState, action) } finally { isDispatching = false } var listeners = currentListeners = nextListeners for (var i = 0; i < listeners.length; i++) { var listener = listeners[i] listener() } return action }
這個函數是用來觸發狀態改變的,他接受一個 action 對象作為參數,然后 reducer 根據 action 的屬性 以及 當前 store 的狀態來生成一個新的狀態,賦予當前狀態,改變 store 的狀態;
即 currentState = currentReducer(currentState, action);
這里的 currentReducer 是一個函數,他接受兩個參數:當前狀態 和 action,然后返回計算出來的新的狀態;
然后遍歷 nextListeners 列表,調用每個監聽函數;
function replaceReducer(nextReducer) { // 判斷參數是否是函數類型 if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function.") } currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) }
這個函數可以替換 store 當前的 reducer 函數,首先直接把 currentReducer = nextReducer,直接替換;
然后 dispatch({ type: ActionTypes.INIT }) ,用來初始化替換后 reducer 生成的初始化狀態并且賦予 store 的狀態;
function observable() { var 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() var unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } } }
對于這個函數,是不直接暴露給開發者的,它提供了給其他觀察者模式/響應式庫的交互操作,具體可看 https://github.com/zenparsing/es-observable
最后執行 dispatch({ type: ActionTypes.INIT }),用來根據 reducer 初始化 store 的狀態。
compose.jscompose 可以接受一組函數參數,從右到左來組合多個函數,然后返回一個組合函數;
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }applyMiddleware.js
export default function applyMiddleware(...middlewares) { // 這個返回的函數就是 enhancer,接受 createStore 函數,再返回一個函數,接受的其實只有 reducer 和 preloadedState; return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] // 暴漏 getState 和 dispatch 給 第三方中間價使用 var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // 創造第三方中間件使用 middlewareAPI 后返回的函數組成的數組 chain = middlewares.map(middleware => middleware(middlewareAPI)) // 結合這一組函數 和 dispatch 組成的新的 dispatch,然后這個暴漏給用戶使用,而原有的 store.dispatch 是不變的,但是不暴漏 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
applyMiddleware 函數的作用是組合 多個 中間件等等,然后返回一個函數(enhancer)
還記得在 createStore 中的一段嗎:
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(...);
然后再執行 enhancer(createStore) 繼續之后的操作;
這里 enhancer(createStore) 等同于 (reducer, preloadedState, enhancer) => { ... }
然后再執行 enhancer(createStore)(reducer, preloadedState);
再回到 applyMiddleware ,這里調用了 var store = createStore(reducer, preloadedState, enhancer) ;
如上所見,這里執行的時候已經沒有 enhancer 參數了,因此會再次執行 createStore 函數的全部部分,然后得到一個返回的實例 store;
之后會生成一個新的 dispatch ,先保存下來原生的 dispatch : var dispatch = store.dispatch;
var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }
這一步是把 store 的 getState 和 dispatch 接口暴露給中間件來操作: chain = middlewares.map(middleware => middleware(middlewareAPI));
最后組合 全部中間件的返回值(函數)chain 和 store.dispatch,然后返回新的 dispatch : dispatch = compose(...chain)(store.dispatch)
這里的 dispatch 并不是原有的 store 的,而是經過組合中間件之后新的 dispatch;
最后返回暴露給用戶的接口:
return { ...store, dispatch }
主要還是 store 原有的接口,但是用新的 dispatch 替換了原有的;這個函數其實就是根據中間件和store的接口生成新的 dispatch 然后暴露給用戶。
combineReducers.js這個函數可以組合一組 reducers(對象) ,然后返回一個新的 reducer 函數給 createStore 使用。
它接受一組 reducers 組成的對象,對象的 key 是對應 value(reducer函數)的狀態名稱;
比如: { userInfo: getUserInfo, list: getList }
userInfo 是根據 getUserInfo 函數計算出來的;
那么 store 里面的 state 結構就會是: { userInfo: ..., list: ... }
export default function combineReducers(reducers) { // 根據 reducers 生成最終合法的 finalReducers:value 為 函數 var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var 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] } } var finalReducerKeys = Object.keys(finalReducers) if (process.env.NODE_ENV !== "production") { var unexpectedKeyCache = {} } // 驗證 reducer 是否合法 var sanityError try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e } // 返回最終生成的 reducer return function combination(state = {}, action) { if (sanityError) { throw sanityError } if (process.env.NODE_ENV !== "production") { var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } var hasChanged = false var nextState = {} for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === "undefined") { var errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // 遍歷一遍看是否改變,然后返回原有狀態值或者新的狀態值 return hasChanged ? nextState : state } }
最終返回一個 combination 也就是真正傳入 createStore 的 reducer 函數;
這是一個標準的 reducer 函數,接受一個初始化狀態 和 一個 action 參數;
每次調用的時候會去遍歷 finalReducer (有效的 reducer 列表),獲取列表中每個 reducer 對應的先前狀態: var previousStateForKey = state[key];
看到這里就應該明白傳入的 reducers 組合為什么 key 要和 store 里面的 state 的 key 相對應;
然后得到當前遍歷項的下一個狀態: var nextStateForKey = reducer(previousStateForKey, action) ;
然后把它添加到整體的下一個狀態: nextState[key] = nextStateForKey
每次遍歷會判斷整體狀態是否改變: hasChanged = hasChanged || nextStateForKey !== previousStateForKey
在最后,如果沒有改變就返回原有狀態,如果改變了就返回新生成的狀態對象: return hasChanged ? nextState : state。
bindActionCreators.jsbindActionCreators 函數可以生成直接觸發 action 的函數;
實質上它只說做了這么一個操作 bindActionFoo = (...args) => dispatch(actionCreator(...args))
因此我們直接調用 bindActionFoo 函數就可以改變狀態了;
接受兩個參數,一個是 actionCreators( actionCreator 組成的對象,key 對于生成的函數名/或者是一個 actionCreator ),一個是 dispatch, store 實例中獲取;
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } export default function bindActionCreators(actionCreators, dispatch) { // 是一個函數,直接返回一個 bindActionCreator 函數,這個函數調用 dispatch 觸發 action 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"?` ) } // 遍歷對象,然后對每個遍歷項的 actionCreator 生成函數,將函數按照原來的 key 值放到一個對象中,最后返回這個對象 var keys = Object.keys(actionCreators) var boundActionCreators = {} for (var i = 0; i < keys.length; i++) { var key = keys[i] var actionCreator = actionCreators[key] if (typeof actionCreator === "function") { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }總結
閱讀了一遍 Redux 的源碼,實在是太精妙了,少依賴,少耦合,純函數式。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92349.html
摘要:的中間件主要是通過模塊實現的。返回的也是一個對象這個其實就是,各個中間件的最底層第三層的哪個函數組成的圓環函數構成的這就是對源碼的一個整體解讀,水平有限,歡迎拍磚。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 applyMiddleware源碼解析 中間件機制在redux中是強大且便捷的,利用redux的中間件我們能夠實現日志記錄,異步調用等多種十分實用的功能。redux的中間件主要是...
摘要:的返回值是函數,這個函數經調用,傳入參數,之后會在中間件鏈上進行傳遞,只要保證每個中間件的參數是并且將傳遞給下一個中間件。 了解了Redux原理之后,我很好奇Redux中間件是怎么運作的,于是選了最常用的redux-thunk進行源碼分析。 此次分析用的redux-thunk源碼版本是2.2.0,redux源碼版本是3.7.2。并且需要了解Redux原理 redux中間件都是由redu...
摘要:源碼解析模塊的代碼十分簡練,但是實現的作用卻是十分強大。只傳遞一個參數的時候,就直接把這個函數返回返回組合函數這就是對源碼的一個整體解讀,水平有限,歡迎拍磚。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 compose源碼解析 compose模塊的代碼十分簡練,但是實現的作用卻是十分強大。redux為何稱為redux?有人說就是reduce和flux的結合體,而reduce正是comp...
摘要:源碼解析是最核心的模塊。比如,當我們需要使用中間件的時候,就會像第三個參數傳遞一個返回值是一個。后續的源碼解讀和測試例子可以關注源碼解讀倉庫 createStore源碼解析 createStore是redux最核心的模塊。這個模塊就是用于創建一個store對象,同時,對外暴露出dispatch,getState,subscribe和replaceReducer方法。(源碼中關于obse...
摘要:要應用于生成環境必須要用或者,是的進化產物,優于。我們來看一下他的源碼,從而學一些東西。里面都是一個一個的模塊,一共個模塊,都導出了一些的方法,比如這個號函數,一個匿名函數,然后導出他寫的方法。整體架構就是這樣的。主要用于壓縮的時候。 redux很小的一個框架,是從flux演變過來的,盡管只有775行,但是它的功能很重要。react要應用于生成環境必須要用flux或者redux,red...
摘要:前言的源碼是我閱讀過的一些庫的源碼中,相對簡單的。在更新完成后,同時會更新,并依次執行監聽者列表。使用新的替換現有的,同時執行是隨機的字符串。會為注冊監聽器,監聽器存儲在數組中,返回的函數則會注銷監聽器。使用管道,將逐層的進行包裝 showImg(https://segmentfault.com/img/remote/1460000019425043?w=1380&h=810); sh...
閱讀 535·2019-08-30 15:55
閱讀 944·2019-08-29 15:35
閱讀 1199·2019-08-29 13:48
閱讀 1910·2019-08-26 13:29
閱讀 2933·2019-08-23 18:26
閱讀 1237·2019-08-23 18:20
閱讀 2834·2019-08-23 16:43
閱讀 2710·2019-08-23 15:58