摘要:函數組合,科里化的串聯結合示例源碼,實現也很優雅,對于返回的,將等參數傳遞進去,然后執行,等待回調異步完成再。對于正常對象則進行下一步。
前言作為前端狀態管理器,這個比較跨時代的工具庫redux有很多實現和思想值得我們思考。在深入源碼之前,我們可以相關注下一些常見問題,這樣帶著問題去看實現,也能更加清晰的了解。
常見問題大概看了下主要有這么幾個:
redux三大原則
這個可以直接參考官方文檔
redux 的優缺點。 關于優缺點,太主觀了大家見仁見智。
redux中間件相關,洋蔥模型是什么,常見中間件。
有關acton,reducer相關的部分可以看我前面的文章。我們主要關注針對store和中間件相關的部分來解讀。
store的創建作為維護和管理數據的部分,store在redux中的作用十分重要。在action發出指令,reduxer進行數據更新之后,監聽數據變化和同步數據更新的操作都要借助store來實現。
createStore 輸入和輸出首先看下createStore的使用,即常見的就是接受經過combineReducers處理之后的reducer和初始的state
import reducer from "./reducers"
const store = createStore(reducer,initialState)
此外還可以接受第三個參數enhancer(增強器,一般就是applyMiddleware)
/**
* 創建管理state 樹的Redux store
* 應用中只能存在一個store,為了區分不同action對應的reducer,
* 可以通過combineReducers來關聯不同的reducer
* @param {Function} reducer combineReducers關聯之后的reducer
* @param {Object} preloadedState 初始state
* @param {Function} enhancer 可以增強store功能的函數,例如中間件等。唯一適合
* @returns 返回一個Store 以維護state和監聽變化
*/
export default function createStore(reducer, preloadedState, enhancer) {
// 如果第二個參數為func,redux認為忽略了初始state,而是
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
// enhancer增強劑,即利用中間件等來增強redux能力
enhancer = preloadedState
preloadedState = undefined
}
// 返回具有dispatch等屬性的對象 即store
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
按照一般的執行順序,我們先看下對于參數的處理(平時大家也是一樣,一個函數,執行之前盡量判斷入參是否符合預期,避免直接處理造成的錯誤)
入參處理對于三個參數,后兩個是非必填的,但如果第二個參數是function,reduxe認為其實encher,不然初始狀態是個函數不符合redux的預期,只能這樣處理了。
// 如果第二個參數為func,redux認為忽略了初始state,而是
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
// enhancer增強劑,即利用中間件等來增強redux能力
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== "undefined") {
if (typeof enhancer !== "function") {
throw new Error("Expected the enhancer to be a function.")
}
// 對于存在的enhancer,高階函數函數的用法,
// 接受createStore返回一個增加功能之后的函數,然后再傳入相關reducer得到store。
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== "function") {
throw new Error("Expected the reducer to be a function.")
}
// 一切符合預期,沒有 enhancer,那么初始賦值
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
// 監聽隊列
let nextListeners = currentListeners
// dispatch標識
let isDispatching = false
// 初始狀態更新之后,聲明init狀態完成。
dispatch({ type: ActionTypes.INIT })
dispatch的實現
dispatch的作用就是根據action,執行對應reducer以更新state。并執行監聽隊列。
下面就來看dispatch的用法和實現。
常見使用:
// redux要求 參數必須為純對象
dispatch({ type: ActionTypes.INIT })
那么對于純對象,redux做了些什么呢
/**
* 通知方法,參數為純的js對象,標明更新內容
* @param {Object} action
*/
function dispatch(action) {
// 是否滿足純凈對象
if (!isPlainObject(action)) {
throw new Error(
"省略"
)
}
// 必須的type是否存在
if (typeof action.type === "undefined") {
throw new Error(
"省略"
)
}
// 判斷是否處于某個action的dispatch中,大家一起dispatch可能死循環
if (isDispatching) {
throw new Error("Reducers may not dispatch actions.")
}
try {
// 開始dispatch,加鎖,標明狀態
isDispatching = true
// 將當前狀態和更新action,傳給當前reducer處理
// 這里可以回想下我們reducer中的兩個參數,state和action 對應的是什么
/**
* const todos = (state = [], action) => {
*/
currentState = currentReducer(currentState, action)
} finally {
// 有異常,鎖置為false,不影響別的dispatch
isDispatching = false
}
// 執行dispatch,并且更新當前監聽隊列為 最新隊列
const listeners = (currentListeners = nextListeners)
// 依次執行,監聽器
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
createStore初始化完成之后會執行dispatch({ type: ActionTypes.INIT }),此時執行初始化操作。
我們要關注下currentState的計算,
將currentState,action傳給reducer處理,然后更新currentState。
針對初始化來說currentState其實就是initState:
// 初始化狀態
let currentState = preloadedState
/****省略***/
// 這里可以回想下我們reducer中的兩個參數,state和action對應的值
currentState = currentReducer(currentState, action)
reducer示例:
const todos = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
}
getSate實現
getState就是獲得store的state。這個比較簡單。當結合react-redux使用時,其會幫我們進行操作。我們就不用自行調用這個方法了,所以不要疑惑從哪里獲取的state。
/**
* 返回應用當前狀態
* 不過需要看下當前是否有更新正在進行,是的話則提示
*/
function getState() {
// 判斷是否isDispatching 中
if (isDispatching) {
throw new Error("省略")
}
return currentState
}
subscribe
subscribe是比較重要的一個方法,用來供我們監聽狀態的變化,以執行相關操作。
例如react-redux中的handleChange 會對是否pure組件及state進行對比,以提升渲染效率。
示例:
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
實現: 返回的是一個函數,可以進行unsubscribe操作。
/**
* 訂閱通知
*/
function subscribe(listener) {
if (typeof listener !== "function") {
throw new Error("Expected the listener to be a function.")
}
if (isDispatching) {
throw new Error(
"省略"
)
}
// 是否已經監聽過
let isSubscribed = true
// 監聽隊列是否相同,區分開,操作nextListeners
ensureCanMutateNextListeners()
// 新增監聽事件
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
"省略"
)
}
// 注冊完成,可以進行取消操作
isSubscribed = false
// 保持事件隊列 同步
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 刪除監聽事件
nextListeners.splice(index, 1)
}
}
replaceReducer
這個開發比較少用,用于熱更新
// 用于reducer的熱替換,開發中一般不會直接使用
function replaceReducer(nextReducer) {
if (typeof nextReducer !== "function") {
throw new Error("Expected the nextReducer to be a function.")
}
currentReducer = nextReducer
// 更新值之后,進行dispatch。
dispatch({ type: ActionTypes.REPLACE })
}
到這里createStore已經解析完成了,大家應該了解該方法到底做了些什么操作吧。
簡單概括一下就是:接收reducer和initState,返回一個store 對象。該對象提供了監聽、分發等功能,以實現數據的更新。
經過上面的解讀之后,對于redux的常規應用應該有所了解了。不過實際使用中可能會遇到些問題。
例如action要求是純對象,而我們獲取數據一般是異步的,這就需要借助redux-thunk這個中間件了。
actionCreater返回一個函數。如下:
export function func1() {
return dispatch => {
dispatch({
type:"test",
data:"a"
})
}
}
在了解如何實現之前,需要先看下redux中間件的原理。 因為reducer更多的關注的是數據的操作,對于一些公共的方法,需要抽離出來,不過這些方法在何時使用呢,redux為我們提供了中間件來滿足需求。
redux中間件原理redux 借鑒了 Koa里 middleware 的思想,即鼎鼎大名的洋蔥模型。
不過這里請求對應的是dispatch的過程。
每次dispatch的過程中,都要依次將中間件執行一遍。
遇到阻塞或者操作完成,執行下個中間件,直到執行完成,以便我們事先日志,監控、異常上報等功能。
那么redux 又是如何支持中間件的呢。這就離不開applyMiddleware了。
這里前面的
實現思想比較簡單,通過科里化和compose,為符合規范的中間件分配訪問dispatch和store的途徑,以便在不同階段來自定義數據更新。
例如異步操作,返回的不是對象,那么就執行返回的函數,然后調用下一個中間件。等異步請求結束,再次dispatch 對應的action。
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.`
)
}
// 賦予每個中間件訪問store的能力。
const middlewareAPI = {
getState: store.getState,
// 箭頭函數保存dispatch,保證其的同步更新
dispatch: (...args) => dispatch(...args)
}
// 串聯中間件,并賦予每個中間件訪問dispatch的能力。
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 關聯dispatch與中間件,組合調用之后得到類似下面的新對象
// dispatch = f1(f2(f3(store.dispatch))));
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
這樣執行之后返回的,對象就是增強之后的store了。
compose的實現redux中compose是柯里化函數的一個示例,目的是將函數串聯起來。
/**
* 函數組合,科里化的串聯
*/
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)))
}
結合redux-thunk示例
redux-thunk源碼,實現也很優雅,對于返回的function,將dispatch等參數傳遞進去,然后執行,等待回調異步完成再dispatch。對于正常對象則進行下一步。
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
// 每次dispatch的時候都會進行判斷,如果是個函數,那就執行函數,不再進行下一步吧,這樣就避免了,函數不滿足action要求的問題
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
那么實際使用時,在createStore時加入該中間件即可:
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk";
const store = createStore(
reducer,
applyMiddleware({
...middleware,
thunk})
)
那么到這里對于redux的中間件 也就是問題2,我想大家也比較清楚了。
對于常見中間件可以參考
redux中文文檔
深入React技術棧
加上重讀redux源碼一和帶著問題看 react-redux 源碼實現總算將redux及react-redux重讀了一遍。可能有人會說道這些源碼,看完也會忘,有這個必要嗎。我感覺分情況來看,如果我們只是使用,那么看官方文檔就可以了,當遇到某些疑問好像找不到貼切解釋的時候,不放一看。
此外也是學習大佬們的設計思路和實現方式,有的放矢才能開卷有益。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/6973.html
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應一個文件。總結上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關生態的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態管理,以及Redux-saga中...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應一個文件。總結上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關生態的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態管理,以及Redux-saga中...
摘要:下面會從淺到深,淡淡在閱讀源碼過程中自己的理解。分拆子頁面后,每一個子頁面對應一個文件。總結上面就是最早版本的源碼,很簡潔的使用了等其目的也很簡單簡化相關生態的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯的,大大提升了開發效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態管理,以及Redux-saga中...
摘要:定場詩守法朝朝憂悶,強梁夜夜歡歌損人利己騎馬騾,正值公平挨餓修橋補路瞎眼,殺人放火兒多我到西天問我佛,佛說我也沒轍前言讀學習數據結構與算法第章數組,本小節將繼續為各位小伙伴分享數組的相關知識數組的新功能。 定場詩 守法朝朝憂悶,強梁夜夜歡歌; 損人利己騎馬騾,正值公平挨餓; 修橋補路瞎眼,殺人放火兒多; 我到西天問我佛,佛說:我也沒轍! 前言 讀《學習JavaScript數據結構與算法...
閱讀 1484·2021-11-16 11:44
閱讀 3299·2021-09-29 09:43
閱讀 632·2019-08-30 10:52
閱讀 952·2019-08-29 11:01
閱讀 3265·2019-08-26 11:47
閱讀 2901·2019-08-23 12:18
閱讀 1373·2019-08-22 17:04
閱讀 2059·2019-08-21 17:04