摘要:假如說是士兵的話那么就是將軍,來管理士兵的狀態。對于來說,也就是一個最外層的容器,全部作為該容器組件的類似于,這樣一來發生變化,就會重新渲染整個頁面,從而達到更新的效果。得益于,每次并不用更新整個樹。規定,對外不暴露任何修改數據的接口。
redux 為什么引入redux
以react來說,state可以包含內部的狀態以及業務數據,對于一個復雜的應用來說,state非常難以管理,一個model的變化可能引起另一個model的變化...依次下去,造成了難以維護的情況,別人很難一下子摸透你的state到底是怎么變得?所以需要引入一個東西來做數據管理,使數據變化變得清晰,redux做的就是這件事情。假如說react是士兵的話那么redux就是將軍,來管理士兵的狀態。
Flux與redux FluxFlux是facebook提出的一種架構思想,與react一樣,強調的是單向數據流,由三大部分組成:
store來保存數據,一個數據對應一個store,每一個store有一個監聽器
dispatcher,含有兩個方法:
register,注冊事件
dispatch,分發一個action使store變化
view,與flux搭配不僅僅是react還可以是vue等,為當前頁面數據的一個展現,與數據保持一致
flux并不是一個mvc框架,flux沒有明確的contoller,但是卻有著一個controller-view,類似于mvvm中的vm(view=vm(model))。對于react來說,也就是一個最外層的容器,store全部作為該容器組件的state(類似于
flux規定store,store對外不暴露任何修改數據的接口。
reduxfacebook提供的flux包基本只有一個dispatcher的實現,意味著我們需要為對每一個store進行一次定義并且創建一個dispatcher實例,需要register一次,dispatch時也需要區分不同的store(聽著就麻煩)。基于flux思想的redux為我們做了簡化。
redux與Flux的區別redux提倡的是單一數據源,也就是一個應用就有一個store,包含著許多的reducer,reducer根據傳入的action來決定怎么改變當前狀態。關于redux,大家可以直接去看文檔,說的很清楚詳細,下面來看一下redux的源碼:
入口index.js為redux的入口文件,暴露出來了redux所提供的API:
export { createStore, // 創建store combineReducers, // 合并reducer bindActionCreators, applyMiddleware, compose }
其中還有一個isCrushed函數,其作用就是做環境的檢測,假如在非生產環境中使用了壓縮的redux,則提出警告,判斷方式也很簡單:
isCrushed.name !== "isCrushed" // 壓縮后函數名字會變短
下面來一個一個的看redux暴露出API的實現:
composecompose源自于函數式編程,redux將其用于用于store的增強,將傳入的函數從右到左的執行,避免了層層嵌套,舉個例子:
const x = 10, add = x => x + 10, sub = x => x - 20, sup = x => x * 10; // 原生方式嵌套實現 add( sup( sub( add(x) ) ) ); // 利用compose改進 const fn = compose(add, sup, sub, add); fn(x);
對比上面代碼,利用compose會使代碼變得清晰,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)) ) ); };createStore 什么是store
redux強調的是單一數據源,把所有的state放入到一棵object tree中,這棵樹只能唯一的存在于一個store中,也就是說redux強調整個應用只有一個store。store可以包含
解析依賴函數該模塊依賴了lodash的isPlainObject,該函數用來判斷對象是否繼承了自定義類,實現起來非常簡單:
function isPlainObject(val) { // 非對象的情況直接返回false if (!isObjectLike(value) || baseGetTag(value) != "[object Object]") { return false } const proto = Object.getPrototypeOf(value) // 針對Object.create(null)創建出來的對象 if (proto === null) { return true } const Ctor = hasOwnProperty.call(proto, "constructor") && proto.constructor // prototype.constructor === Object return typeof Ctor == "function" && Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString }主體函數
// 內部的action,用于reset export const ActionTypes = { INIT: "@@redux/INIT" }; /* * 創建store * reducer reducer函數 * preloadedState 初始狀態 * enhancer 增強函數,對createStoren能力進行增強,如devtools */ export default function createStore(reducer, preloadedState, enhancer) { // 參數修正 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."); } // 返回已經增強后的store 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; // 監聽store變化的監聽器(一些回調函數) let currentListeners = []; let nextListeners = currentListeners; // 是否處于dispatch的過程中 let isDispatching = false; /**** 看下面文字解釋 ****/ function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice(); } } // 給store新增一個監聽器 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.index(listener); nextListeners.splice(index, 1); }; } // 返回當前的狀態 function getState() { return currentState; } 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; // 根據recuder來更新當前狀態 currentState = currentReducer(currentState, action); } finally { isDispatching = false; } const listeners = currentListeners = nextListeners; // 發布事件 for (let i = 0; i < listeners.length; i++) { const listener = listeners; listener(); } return action; } // 更新當前reducer,重置store function replaceReducer(nextReducer) { if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function."); } currentReducer = nextReducer; dispatch({ type: ActionTypes.INIT }); } // 初始化store dispatch({ type: ActionTypes.INIT }); // 與Rxjs這種交互,根本看不懂-- function observable() { ... } return { getState, subscribe, dispatch, replaceReducer, [$$observable]: observable }; };
需要注意的有以下幾點:
觀察源碼可以發現,currentListeners與nextListeners存儲的都是監聽函數,這樣做的目的是保證dispatch的過程不發生錯誤,加入使用一個隊列的話,當執行過程中有監聽函數被移除時,則會發生錯誤,如:當前監聽函數隊列為:[a, b, c],當執行回調函數a后將其移除,則隊列發生改變[b, c],而利用for循環來執行回調,執行到i = 2時會拋出錯誤。
forEach比for差在:
ie8不兼容forEach (ie8 is out)
forEach不能提前終止,但是在dispatch中無此問題
性能,這是forEach最大的問題,for要比forEach快的多的多(差不多一倍左右),查看v8代碼也可以感覺出,forEach本質上做的還是for循環,每次loop進行一次判斷和函數調用,自然速度會慢。
applyMiddleware中間件,express與koa也就中間件,express中的中間件處理的請求到響應的這一過程,redux中的中間件處理的是從action發出到reducer接收到action這一過程,在這個過程中可以利用中間件進行寫日志,處理異步操作等過程,作用就是來增強createStore,給其添加更多的功能。先來看下下面這個簡化的例子:
const calc = (obj) => obj.value + 20; // 簡單的模擬下stroe const obj = { calc }; // 加強fn函數 function applyMiddleware(fn, middlewares) { middlewares = middlewares.slice(); middlewares.reverse(); // 每次改變fn,一次擴展一個功能 let { calc } = obj; middlewares.forEach( middleware => calc = middleware(obj)(calc) ); return calc; } // arrow function is cool!!! const logger = obj => next => num => { console.log(`num is ${num.value}`); let result = next(num); console.log(`finish calc, result is ${result}`); return result; }; const check = obj => next => num => { console.log(`typeof num.value is ${typeof num.value}`); let result = next(num); return result; }; const fn = applyMiddleware(obj, [check, logger]); fn({ value: 50 });
在上面簡單的例子中為calc做了兩個功能擴展,執行起來就是一個高階函數check->logger->calc,理解了這個再來看看redux的applyMiddleware的實現:
export default function applyMiddleware(...middlewares) { // 利用閉包保存下來了middlewares return (createStore) => (reducer, preloadadState, enhancer) => { const store = createStore(reducer, preloadadState, enhancer); let dispatch = store.dispatch; let chain = []; // middleware不會改變store,利用閉包保存 const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; // chain中的元素仍未函數 // 接受的參數為`next` => 下一個中間件 chain = middlewares.map(middleware => middleware(middlewareAPI)); // 本身的dispatch放在最后執行 // dispatch仍未函數,接受的參數為`action` // 返回的是一個高階函數,需要注意的是中間件并不會改變原本的action // dispatch變成了一個升級版 dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch }; }; };
附一張圖:
applyMiddleware代碼雖少,但是卻是最難理解的部分
redux-thunk是一個十分剪短有用的中間件,尤其是在異步應用中,利用redux-thunk可以使action變為一個函數,而不僅僅是一個對象,并且在該函數中仍然可以觸發dispatch:
// 讓action可以成為函數 function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { // action為函數類型,執行action,dispatch結束 if (typeof action === "function") { return action(dispatch, getState, extraArgument); } return next(action); }; }combineReducers
combineReducers用來合并多個reducer:
assertReducerSanity來驗證reducer利用createStore文件中定義的ActionTypes來進行初始化state,也就是定義的reducer會在一開始執行一遍,而后對得到state進行檢測,為undefined拋出異常,同時利用隨機字符串測試,防止其處理type為@@redux/INIT而未處理default的情況。
combineReducers將我們所寫的reducer進行合并,返回一個函數,每次dispatch時,執行函數,遍歷所有的reducer,計算出最終的state:
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} // 有效reducer集合 // 驗證reducer,必須是純函數才有效 for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (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 (NODE_ENV !== "production") { unexpectedKeyCache = {} } let sanityError try { // 檢測reducer assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } // getUnexpectedStateShapeWarningMessage // 檢測state類型是否為0繼承對象 // 用于找出多余的redcuer并給出警告 if (NODE_ENV !== "production") { const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} // 每次發出一個action會遍歷所有的reducer for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] // 保留之前的state與新生成的state做對比 const reducer = finalReducers[key] const previousStateForKey = state[key] const next StateForKey = reducer(previousStateForKey, action) // state為undefined拋出異常 if (typeof nextStateForKey === "undefined") { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey // 比較是否數據發生變化 hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // 未發生變化返回之前的數據 return hasChanged ? nextState : state } }bindActionCreators
這個基本上的作用不大,唯一運用的情況就是將其作為props傳入子組件,對于子組件來說可以全然不知redux的存在。如果利用UI庫的組件來操作頁面狀態,bindActionCreators是一個很好的選擇,實現很簡單:
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) }
bindActionCreators僅僅是遍歷所有的actions返回一個鍵值對。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81465.html
摘要:使得在變化和異步中可預測。它是數據的唯一來源。指定了應用狀態的變化如何響應并發送到的,只是描述了有事情發生了這一事實,并沒有描述應用如何更新。更新的函數稱為,它是一個純函數,接受舊的和,返回新的。是和之間的橋梁,是把它們聯系到一起的對象。 為什么使用redux 隨著前端單頁面開發越來越復雜,javascript需要管理越來越多的狀態state。如果一個model的變化引起另一個mode...
摘要:調用鏈中最后一個會接受真實的的方法作為參數,并借此結束調用鏈。總結我們常用的一般是除了和之外的方法,那個理解明白了,對于以后出現的問題會有很大幫助,本文只是針對最基礎的進行解析,之后有機會繼續解析對他的封裝 前言 雖然一直使用redux+react-redux,但是并沒有真正去講redux最基礎的部分理解透徹,我覺得理解明白redux會對react-redux有一個透徹的理解。 其實,...
摘要:本周精讀內容是重新思考。數據流對數據緩存,性能優化,開發體驗優化都有進一步施展的空間,擁抱插件生態是一個良好的發展方向。 本周精讀內容是 《重新思考 Redux》。 1 引言 《重新思考 Redux》是 rematch 作者 Shawn McKay 寫的一篇干貨軟文。 dva 之后,有許多基于 redux 的狀態管理框架,但大部分都很局限,甚至是倒退。但直到看到了 rematch,總算...
閱讀 2964·2023-04-26 02:04
閱讀 1278·2021-11-04 16:07
閱讀 3699·2021-09-22 15:09
閱讀 678·2019-08-30 15:54
閱讀 1899·2019-08-29 14:11
閱讀 2525·2019-08-26 12:19
閱讀 2255·2019-08-26 12:00
閱讀 752·2019-08-26 10:27