React?redux?原理及使用詳解
概述
- 一個(gè)狀態(tài)管理工具
- Store:保存數(shù)據(jù)的地方,你可以把它看成一個(gè)容器,整個(gè)應(yīng)用只能有一個(gè) Store。
- State:包含所有數(shù)據(jù),如果想得到某個(gè)時(shí)點(diǎn)的數(shù)據(jù),就要對(duì) Store 生成快照,這種時(shí)點(diǎn)的數(shù)據(jù)集合,就叫做 State。
- Action:Action 就是 View 發(fā)出的通知,表示 State 應(yīng)該要發(fā)生變化了。
- Action Creator:View 要發(fā)送多少種消息,就會(huì)有多少種 Action。如果都手寫,會(huì)很麻煩,所以我們定義一個(gè)函數(shù)來(lái)生成 Action,這個(gè)函數(shù)就叫 Action Creator。
- Reducer:Store 收到 Action 以后,必須給出一個(gè)新的 State。這種 State 的計(jì)算過(guò)程就叫做 Reducer。Reducer 是一個(gè)函數(shù),它接受 Action 和當(dāng)前 State 作為參數(shù),返回一個(gè)新的 State。
- dispatch:是 View 發(fā)出 Action 的唯一方法。
整個(gè)工作流程:
- 首先,用戶(通過(guò) View)發(fā)出 Action,發(fā)出方式就用到了 dispatch 方法。
- 然后,Store 自動(dòng)調(diào)用 Reducer,并且傳入兩個(gè)參數(shù):當(dāng)前 State 和收到的 Action,Reducer 會(huì)返回新的 State
- State 一旦有變化,Store 就會(huì)調(diào)用監(jiān)聽(tīng)函數(shù),來(lái)更新 View。
三大原則
1.單一數(shù)據(jù)源(Store) 整個(gè)應(yīng)用的State被存放在一棵Object tree中,并且這個(gè)Object tree只存在唯一一個(gè)Store中;
2.State是只讀的 唯一改變 State 的方法就是觸發(fā) Action,Action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象。 確保了所有的修改都能被集中化處理。
3.通過(guò)純函數(shù)Reducer來(lái)修改Store, Reducer 只是一些純函數(shù),它接收先前的 State 和 Action,并返回新的 State。 即reducer(state, action) => new state
createStore創(chuàng)建store
- createStore 方法接受 3 個(gè)參數(shù)參數(shù) (reducer, [preloadedState], enhancer);
返回 store,store 上掛載著 dispatch、getState、subscribe、replaceReducer 等方法 - 第二個(gè)參數(shù)是 preloadedState,它是 state 的初始值,實(shí)際上他并不僅僅是扮演著一個(gè) initialState 的角色,如果我們第二個(gè)參數(shù)是函數(shù)類型,createStore 會(huì)認(rèn)為傳入了一個(gè) enhancer,如果我們傳入了一個(gè) enhancer,createStore 會(huì)返回 enhancer(createStore)(reducer, preloadedState)的調(diào)用結(jié)果,這是常見(jiàn)高階函數(shù)的調(diào)用方式。
- enhancer 接受 createStore 作為參數(shù),對(duì) createStore 的能力進(jìn)行增強(qiáng),并返回增強(qiáng)后的 createStore。然后再將 reducer 和 preloadedState 作為參數(shù)傳給增強(qiáng)后的 createStore,最終得到生成的 store。
export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { // 第二個(gè)參數(shù)是一個(gè)函數(shù),沒(méi)有第三個(gè)參數(shù)的情況 enhancer = preloadedState; preloadedState = undefined; } // 如果第三個(gè)參數(shù)是函數(shù)走下面的邏輯,返回一個(gè)新的createStore if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { // enhancer 不是函數(shù)就報(bào)錯(cuò) throw new Error('Expected the enhancer to be a function.'); } // enhancer就是高階函數(shù),強(qiáng)化了本身這個(gè)createStore的函數(shù),拿到增強(qiáng)后的createStore函數(shù)去處理 // applyMiddleware這個(gè)函數(shù)還會(huì)涉及到這個(gè) return enhancer(createStore)(reducer, preloadedState); } if (typeof reducer !== 'function') { // reducer不是函數(shù)報(bào)錯(cuò) throw new Error('Expected the reducer to be a function.'); } // 其他代碼省略 return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable, }; }
applyMiddleware 應(yīng)用中間件
- 返回一個(gè)函數(shù) enhancer;
- 右邊中間件的執(zhí)行時(shí)機(jī)由左邊的中間件決定,這是因?yàn)?next 的方法的調(diào)用時(shí)機(jī)由右邊控制
- dispatch 的處理使用了閉包,這樣保證在中間件中調(diào)用 dispatch 也是用的最終的 dispatch,也是同一個(gè) dispatch;
- 寫中間件的時(shí)候如果要調(diào)用 dispatch,一定要有跳出條件,防止死循環(huán)
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, }; }; } // 其實(shí)就是修改了dispatch
let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);
combineReducers 合并多個(gè)reducer
從執(zhí)行結(jié)果看,這時(shí)候 state 已經(jīng)變成了一個(gè)以這些 reducer 為 key 的對(duì)象;
reducer 也變成了一個(gè)合并的 reducer;
遍歷執(zhí)行所有的 reducer 的時(shí)候把 action 傳進(jìn)去,返回新的 state;
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 (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key]; } } const finalReducerKeys = Object.keys(finalReducers); /* 返回一個(gè)整合后的reducers */ return function combination(state = {}, action) { 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; }; }
dispatch
- 默認(rèn)的 action 只能是普通對(duì)象,除非使用了第三方中間件,比如 redux-thunk
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; } var listeners = (currentListeners = nextListeners); for (var i = 0; i < listeners.length; i++) { var listener = listeners[i]; listener(); } return action; }
中間件
- Redux 中間件在發(fā)起一個(gè) action 至 action 到達(dá) reducer 的之間提供了一個(gè)第三方的擴(kuò)展。本質(zhì)上通過(guò)插件的形式,將原本的 action->redux 的流程改變?yōu)?action->middleware1->middleware2-> ... ->reducer,通過(guò)改變數(shù)據(jù)流,從而實(shí)現(xiàn)例如異步 Action、日志輸入的功能。
- Redux 中間件范式
- 一個(gè)中間件接收 store 作為參數(shù),會(huì)返回一個(gè)函數(shù)
- 返回的這個(gè)函數(shù)接收老的 dispatch 函數(shù)作為參數(shù)(一般用 next 作為形參),會(huì)返回一個(gè)新的函數(shù)
- 返回的新函數(shù)就是新的 dispatch 函數(shù),這個(gè)函數(shù)里面可以拿到外面兩層傳進(jìn)來(lái)的 store 和老 dispatch 函數(shù)
function logger(store) { return function (next) { return function (action) { // 新的 dispatch 函數(shù) console.group(action.type); console.info('dispatching', action); let result = next(action); console.log('next state', store.getState()); console.groupEnd(); return result; }; }; }
中間件的調(diào)用順序
- 首先調(diào)用順序和書寫順序是一致的,但是這里面的洋蔥模型包含了兩次順序,從洋蔥出來(lái)的順序是和書寫順序相反
import React from 'react'; import { createStore, applyMiddleware } from 'redux'; function createLogger({ getState, dispatch }) { return (next) => (action) => { const prevState = getState(); console.log('createLogger1'); const returnValue = next(action); const nextState = getState(); const actionType = String(action.type); const message = `action ${actionType}`; console.log(`%c prev state`, `color: #9E9E9E`, prevState); console.log(`%c action`, `color: #03A9F4`, action); console.log(`%c next state`, `color: #4CAF50`, nextState); return returnValue; }; } function createLogger2({ getState }) { return (next) => (action) => { const console = window.console; const prevState = getState(); console.log('createLogger2'); const returnValue = next(action); const nextState = getState(); const actionType = String(action.type); const message = `action ${actionType}`; console.log(`%c prev state2`, `color: #9E9E9E`, prevState); console.log(`%c action2`, `color: #03A9F4`, action); console.log(`%c next state2`, `color: #4CAF50`, nextState); return returnValue; }; } const reducer = function (state = { number: 0 }, action) { switch (action.type) { case 'add': return { number: state.number + action.number, }; default: return state; } }; const store = createStore( reducer, applyMiddleware(createLogger, createLogger2), ); store.subscribe(function () { console.log(1111); }); const { dispatch } = store; const App = () => { const handler = () => { dispatch({ type: 'add', number: 10 }); }; return ( <div> <button onClick={handler}>觸發(fā)redux</button> </div> ); }; export default App;
store
store 的屬性如下:
dispatch: ƒ dispatch(action)
getState: ƒ getState()
replaceReducer: ƒ replaceReducer(nextReducer)
subscribe: ƒ subscribe(listener)
redux 數(shù)據(jù)流
Redux 的數(shù)據(jù)流是這樣的:
界面 => action => reducer => store => react => virtual dom => 界面
bindActionCreators
將action對(duì)象轉(zhuǎn)為一個(gè)帶dispatch的方法
比如connect接收的mapDispatchToProps 是對(duì)象,會(huì)使用 bindActionCreators 處理; 接收 actionCreator 和 dispatch,返回一個(gè)函數(shù);
function bindActionCreator(actionCreator, dispatch) { // 返回一個(gè)函數(shù) return function() { return dispatch(actionCreator.apply(this, arguments)) } } function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } const boundActionCreators = {} for (const key in actionCreators) { const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators } const mapDispatchToProps = { // actionCreators 這是個(gè)集合, onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; } 轉(zhuǎn)換為: const mapDispatchToProps = { // actionCreators 這是個(gè)集合, onClick:function(filter) { return dispatch({ // dispatch 是閉包中的方法 type: 'SET_VISIBILITY_FILTER', filter: filter }) } }
compose
函數(shù)套函數(shù),compose(...chain)(store.dispatch)結(jié)果返回一個(gè)加強(qiáng)了的 dispatch;
這點(diǎn)和koa比較相似,這個(gè) dispatch 在執(zhí)行的時(shí)候會(huì)調(diào)用中間件。
function compose(...funcs) { if (funcs.length === 0) { return (arg) => arg; } if (funcs.length === 1) { return funcs[0]; } // 每一次reduce迭代都會(huì)返回一個(gè)加強(qiáng)版的dispatch return funcs.reduce( (a, b) => (...args) => a(b(...args)), ); }
加強(qiáng)版 dispatch(一個(gè)方法,接收 action 參數(shù)),在中間件中用 next 表示,執(zhí)行 next 之后,會(huì)形成一個(gè)鏈條。
enhancer
- enhancer,翻譯過(guò)來(lái)就是 store 加強(qiáng)器,比如 applymiddleware 的返回值就是一個(gè) enhaner。store 加強(qiáng)器可以重新構(gòu)建一個(gè)更強(qiáng)大的 store,來(lái)替換之前的基礎(chǔ)版的 store,讓你的程序可以增加很多別的功能,比如 appllymiddleware 可以給你的 redux 增加中間件,使之可以擁有異步功能,日志功能等!
// 以createStore為參數(shù) (createStore) => (...args) => {};
使用 redux 常遇見(jiàn)的問(wèn)題
- 樣板代碼過(guò)多 增加一個(gè) action 往往需要同時(shí)定義相應(yīng)的 actionType 然后再寫相關(guān)的 reducer。例如當(dāng)添加一個(gè)異步加載事件時(shí),需要同時(shí)定義加載中、加載失敗以及加載完成三個(gè) actionType,需要一個(gè)相對(duì)應(yīng)的 reducer 通過(guò) switch 分支來(lái)處理對(duì)應(yīng)的 actionType,冗余代碼過(guò)多;
- 目前已經(jīng)存在著非常多的解決方案,比如dva redux-tookit等。
- 更新效率問(wèn)題:由于使用不可變數(shù)據(jù)模式,每次更新 state 都需要拷貝一份完整的 state 造成了內(nèi)存的浪費(fèi)以及性能的損耗。
- 其實(shí) redux 以及 react-redux 中內(nèi)部已做優(yōu)化,開(kāi)發(fā)的時(shí)候使用 shouldComponentUpdate 等優(yōu)化方法也可以應(yīng)用,也可以用不可變數(shù)據(jù)結(jié)構(gòu)如 immutable、Immr 等來(lái)解決拷貝開(kāi)銷問(wèn)題。
以上就是React redux 原理及使用詳解的詳細(xì)內(nèi)容,更多關(guān)于React redux原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一文教你如何避免React中常見(jiàn)的8個(gè)錯(cuò)誤
這篇文章主要來(lái)和大家一起分享在?React?開(kāi)發(fā)中常見(jiàn)的一些錯(cuò)誤,以及如何避免這些錯(cuò)誤,理解這些問(wèn)題背后的細(xì)節(jié),防止犯下類似的錯(cuò)誤,需要的可以參考下2023-12-12Shopee在React?Native?架構(gòu)方面的探索及發(fā)展歷程
這篇文章主要介紹了Shopee在React?Native?架構(gòu)方面的探索,本文會(huì)從發(fā)展歷史、架構(gòu)模型、系統(tǒng)設(shè)計(jì)、遷移方案四個(gè)方向逐一介紹我們?nèi)绾我徊讲降貪M足多團(tuán)隊(duì)在復(fù)雜業(yè)務(wù)中的開(kāi)發(fā)需求,需要的朋友可以參考下2022-07-07關(guān)于react-router/react-router-dom v4 history不能訪問(wèn)問(wèn)題的解決
這篇文章主要給大家介紹了關(guān)于react-router/react-router-dom v4 history不能訪問(wèn)問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01react組件中debounce防抖功能失效問(wèn)題解決辦法
在React組件中,如果使用useState管理searchKey,每次輸入變化都會(huì)導(dǎo)致組件重新渲染,從而生成新的debounce函數(shù),導(dǎo)致防抖功能失效,解決方法是使用useRef定義searchKey為非響應(yīng)式數(shù)據(jù),從而維持debounce函數(shù)的穩(wěn)定,確保防抖功能有效,感興趣的朋友跟隨小編一起看看吧2024-10-10