Redux 是可預(yù)測的狀態(tài)管理框架,它很好的解決多交互,多數(shù)據(jù)源的訴求。
三大原則:
單一數(shù)據(jù)源:
整個應(yīng)用的state被存儲在一顆object tree中,并且這顆 object tree存在于唯一的store里,store就是存儲數(shù)據(jù)的容器,容器里面會維護(hù)整個應(yīng)用的state。store提供四個API:dispatch subscribe getState replaceReducer。 數(shù)據(jù)源是唯一的,所以獲取數(shù)據(jù)的唯一方式就是store.getState()。
state只讀:
根據(jù)state只讀原則,state的變更要通過store的dispatch(action)方法,action就是變更數(shù)據(jù)的載體,action = {type: '', payload},type是變更數(shù)據(jù)的唯一標(biāo)志, payload是需要變更的數(shù)據(jù)。
使用純函數(shù)變更state
reducer是純函數(shù),接受兩個參數(shù)即 state和action, 根據(jù)action的type屬性執(zhí)行對應(yīng)的代碼,更改相應(yīng)的數(shù)據(jù),然后返回新的state。
完整的數(shù)據(jù)流過程:
view層觸發(fā)actionCreator, actionCreator通過store.dispatch(action) ,reducer執(zhí)行對應(yīng)的代碼,返回新的state,更新頁面。

源碼結(jié)構(gòu):
src
├── utils ---------------------------------------- 工具函數(shù)
├── applyMiddleware.js --------------------------- 加載 middleware
├── bindActionCreators.js ------------------------ 生成將 action creator 包裹在 dispatch 里的函數(shù)
├── combineReducers.js --------------------------- 合并 reducer 函數(shù)
├── compose.js ----------------------------------- 組合函數(shù)
├── createStore.js ------------------------------- 創(chuàng)建一個 Redux store 來儲存應(yīng)用中所有的 state
├── index.js ------------------------------------- 入口 js
index.js
index.js文件是整個代碼的入口,該文件暴露了一些接口供開發(fā)者使用: createStore, combineReducers, bindActionCreators, applyMiddleware, compose,接下來逐個分析這些接口。
createStore
createStore是redux最重要的API,它創(chuàng)建一個store,保存應(yīng)用中的state。
createStore函數(shù)接受三個參數(shù):reducer, preloadedState, enhancer,
返回值:dispatch、subscribe、getState、replaceReducer 和 [$$observable],這就是我們開發(fā)中主要使用的幾個接口。
參數(shù)
reducer: 函數(shù),返回下一個狀態(tài),接收state和action作為參數(shù)
preloadedState: state初始值,可以忽略不傳
enhancer:store的增強(qiáng)器,一般是第三方中間件,返回一個增強(qiáng)后的store creator,這個函數(shù)通常由applyMiddleware函數(shù)來生成。
返回值
dispatch
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 的兼容,但是無論如何都會繼續(xù)執(zhí)行 isDispatching = false 的操作
try {
isDispatching = true
// 使用 currentReducer 來操作傳入 當(dāng)前狀態(tài)和 action,返回處理后的狀態(tài)
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
dispatch函數(shù)是觸發(fā)動作的函數(shù),接收action行為作為參數(shù),action必須是一個對象,且該對象必須有type參數(shù),如果action滿足條件,則調(diào)用currentReducer(其實就是createStore的參數(shù) reducer的引用),currentReducer會根據(jù)action來改變對應(yīng)的值,生成新的store,然后再遍歷nextListeners列表,調(diào)用每一個監(jiān)聽函數(shù)。
getState
// 讀取由 store 管理的狀態(tài)樹
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
這個函數(shù)可以獲取當(dāng)前的狀態(tài),createStore 中的 currentState 儲存當(dāng)前的狀態(tài)樹,這是一個閉包,這個參數(shù)會持久存在,并且所有的操作狀態(tài)都是改變這個引用,getState 函數(shù)返回當(dāng)前的 currentState。
subscribe
function subscribe(listener) {
// 判斷傳入的參數(shù)是否為函數(shù)
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
除去一些邊界條件的判斷,subscribe函數(shù)最主要的是給store狀態(tài)添加監(jiān)聽函數(shù),該函數(shù)接收一個函數(shù)作為參數(shù),會往nextListeners監(jiān)聽列表加入這個函數(shù),然后會返回一個unsubscribe函數(shù),用于解綁,如果解綁,就從nextListeners列表中去掉該函數(shù),一旦調(diào)用dispatch改變store,監(jiān)聽函數(shù)就會全部執(zhí)行。
combineReducers
reducer是管理state的一個模塊,在應(yīng)用初始化的時候,它返回initialState,當(dāng)用戶調(diào)用action時,它會根據(jù)action的type屬性,進(jìn)行相應(yīng)的更新,reducer是純函數(shù),不會更改傳入的state,會返回新的state。
當(dāng)所有的reducer邏輯都寫在同一個reducer函數(shù)里面會非常的龐大,所以我們會將reducer進(jìn)行適當(dāng)?shù)牟鸱?,但是最終傳入createStore里面的是唯一的reducer函數(shù),所以我們傳入createStore前要進(jìn)行合并,就需要combineReducers方法。
參數(shù)
reducers (Object): 一個對象,它的值(value)對應(yīng)不同的 reducer 函數(shù),這些 reducer 函數(shù)后面會被合并成一個。
返回值
(Function): 它是真正 createStore 函數(shù)的 reducer,接受一個初始化狀態(tài)和一個 action 參數(shù);每次調(diào)用的時候會去遍歷 finalReducer(有效的 reducer 列表),然后調(diào)用列表中每個 reducer,最終構(gòu)造一個與 reducers 對象結(jié)構(gòu)相同的 state 對象。
combineReducers用法
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// This would produce the following state object
{
potato: {
// ... potatoes, and other state managed by the potatoReducer ...
},
tomato: {
// ... tomatoes, and other state managed by the tomatoReducer, maybe some nice sauce? ...
}
}
源碼
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 (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]
}
}
const finalReducerKeys = Object.keys(finalReducers)
/*let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}*/
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
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
}
}
combineReducer函數(shù)返回一個combination 函數(shù),該函數(shù)接收state和action作為參數(shù),每次調(diào)用該函數(shù)時:
1、 for (let i = 0; i < finalReducerKeys.length; i++) { ... }:遍歷 finalReducer(有效的 reducer 列表);
2、 var previousStateForKey = state[key]:當(dāng)前遍歷項的之前狀態(tài),看到這里就應(yīng)該明白傳入的 reducers 組合為什么 key 要和 store 里面的 state 的 key 相對應(yīng)了;
3、 var nextStateForKey = reducer(previousStateForKey, action):當(dāng)前遍歷項的下一個狀態(tài);
4、 nextState[key] = nextStateForKey:將 當(dāng)前遍歷項的下一個狀態(tài)添加到 nextState;
5、 hasChanged = hasChanged || nextStateForKey !== previousStateForKey:判斷狀態(tài)是否改變;
6、 return hasChanged ? nextState : state:如果沒有改變就返回原有狀態(tài),如果改變了就返回新生成的狀態(tài)對象。
applyMiddleware
bindActionCreators
- actionCreator創(chuàng)建動作
在分析之前先明確ActionCreator是什么?ActionCreator是動作創(chuàng)造者或者說是動作工廠,如果我們想根據(jù)不同的參數(shù)來生成不同值的計數(shù)器,例子如下:
const counterActionCreator = (step) => {
return {
type: 'increment',
step: step || 1,
}
}
2.bindActionCreator
從上述例子出發(fā),如果我們想生成不同的計數(shù)器,并分發(fā)他們,則需要按照以下寫法:
const action1 = counterActionCreator();
dispatch(action1);
const action2 = counterActionCreator(2);
dispatch(action2);
const action3 = counterActionCreator(3);
dispatch(action3);
從上面的寫法來看,每次都需要去調(diào)用actionCreator,再調(diào)用dispatch分發(fā)action,會產(chǎn)生很多繁雜重復(fù)的代碼,所以我們可以采用 bindActionCreators.js 文件里面的bindActionCreator 方法來優(yōu)化代碼,
bindActionCreator 源碼:
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
bindActionCreator會返回一個函數(shù),函數(shù)中會調(diào)用actionCreator生成action,并將action作為dispatch的參數(shù)分發(fā)掉,所以我們將action的生成動作和調(diào)用動作封裝到了一個函數(shù)里面,我們直接調(diào)用bindActionCreator返回的函數(shù)就行,不用再每次去調(diào)用actionCreator,再調(diào)用dispatch分發(fā)action,例子如下:
const increment = bindActionCreator(counterActionCreator, dispatch);
increment();
increment(2);
increment(3);
- bindActionCreators
接下來看bindActionCreators函數(shù),其實是對bindActionCreator的增強(qiáng),去掉一些判斷條件,源碼部分具體如下:
function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') { // #1
return bindActionCreator(actionCreators, dispatch) // #2
}
....
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') { // #3
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
當(dāng)actionCreators是一個函數(shù)時,直接調(diào)用bindActionCreator將整個過程封裝返回即可
當(dāng)actionCreators是一個有多個函數(shù)方法組成的對象時,遍歷該對象,如果鍵對應(yīng)的值是函數(shù),則調(diào)用bindActionCreator將整個過程封裝,并將封裝結(jié)果賦值給該鍵,最后返回一個對象,對象里面的值都被bindActionCreator封裝過。
actionCreators為對象時的例子如下:
const actionCreators = {
increment: function(step) {
return {
type: 'INCREMENT',
step: step || 1
}
},
decrement: function(step) {
return {
type: 'DECREMENT',
step: - (step || 1)
}
}
}
const newActionCreators = bindActionCreators(MyActionCreators, dispatch)
newActionCreators.increment();
newActionCreators.increment(1);
newActionCreators.decrement();
newActionCreators.decrement(1);