Redux 是 JavaScript 狀態(tài)容器,提供可預(yù)測化的狀態(tài)管理,可以讓你構(gòu)建一致化的應(yīng)用,運(yùn)行于不同的環(huán)境(客戶端绘闷、服務(wù)器、原生應(yīng)用)荸频,并且易于測試。不僅于此客冈,它還提供 超爽的開發(fā)體驗(yàn)旭从,比如有一個時間旅行調(diào)試器可以編輯后實(shí)時預(yù)覽。Redux 除了和 React 一起用外场仲,還支持其它界面庫和悦。 它體小精悍(只有 2kB,包括依賴)
學(xué)習(xí) redux 之前,首先得弄清楚一些概念
1.redux 在 react 開發(fā)中所起到的作用——狀態(tài)集中管理 2.弄清楚 redux 中如何實(shí)現(xiàn)狀態(tài)管理——store燎窘、action摹闽、reducer 三個概念 3.解讀 redux 中源碼的實(shí)現(xiàn)(createStore, combineReducers, bindActionCreators, applyMiddleware, compose, ActionTypes)
三大原則
單一數(shù)據(jù)源
整個應(yīng)用的 state 被存儲在一棵 object tree 中,并且這個 object tree 只存在于唯一一個 store 中褐健。
State 是只讀的
惟一改變 state 的方法就是觸發(fā) action,action 是一個用于描述已發(fā)生事件的普通對象。
使用純函數(shù)來執(zhí)行修改
為了描述 action 如何改變狀態(tài)樹蚜迅,我們需要編寫 reducers舵匾。Reducer 只是一些純函數(shù),他接受先前的 state 和 action谁不,并返回新的 state 對象坐梯。
Action
首先,讓我們來給 action 下個定義刹帕。
Action 是把數(shù)據(jù)從應(yīng)用(譯者注:這里之所以不叫 view 是因?yàn)檫@些數(shù)據(jù)有可能是服務(wù)器響應(yīng)吵血,用戶輸入或其它非 view 的數(shù)據(jù) )傳到 store 的有效載荷。它是 store 數(shù)據(jù)的唯一來源偷溺。一般來說你會通過 store.dispatch() 將 action 傳到 store蹋辅。
添加新 todo 任務(wù)的 action 是這樣的:
ActionTypes.js
const ADD_TODO = "ADD_TODO"
Action.js
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
Action 本質(zhì)上是 JavaScript 普通對象。我們約定挫掏,action 內(nèi)必須使用一個字符串類型的 type 字段來表示將要執(zhí)行的動作侦另。多數(shù)情況下,type 會被定義成字符串常量尉共。當(dāng)應(yīng)用規(guī)模越來越大時褒傅,建議使用單獨(dú)的模塊或文件來存放 action。
import { ADD_TODO } from "../actionTypes"
樣板文件使用提醒
使用單獨(dú)的模塊或文件來定義 action type 常量并不是必須的袄友,甚至根本不需要定義殿托。對于小應(yīng)用來說,使用字符串做 action type 更方便些剧蚣。不過支竹,在大型應(yīng)用中把它們顯式地定義成常量還是利大于弊的。參照 減少樣板代碼 獲取更多保持代碼簡潔的實(shí)踐經(jīng)驗(yàn)券敌。
Action 創(chuàng)建函數(shù)
Action 創(chuàng)建函數(shù) 就是生成 action 的方法唾戚。“action” 和 “action 創(chuàng)建函數(shù)” 這兩個概念很容易混在一起待诅,使用時最好注意區(qū)分叹坦。
在 Redux 中的 action 創(chuàng)建函數(shù)只是簡單的返回一個 action:
Action.js
function addTodo(text) {
return {
type: ADD_TODO,
text,
}
}
這樣做將使 action 創(chuàng)建函數(shù)更容易被移植和測試。
Redux 中只需把 action 創(chuàng)建函數(shù)的結(jié)果傳給 dispatch() 方法即可發(fā)起一次 dispatch 過程卑雁。
dispatch(addTodo(text))
或者創(chuàng)建一個 被綁定的 action 創(chuàng)建函數(shù) 來自動 dispatch:
const boundAddTodo = (text) => dispatch(addTodo(text))
然后直接調(diào)用它們:
boundAddTodo(text)
store 里能直接通過 store.dispatch() 調(diào)用 dispatch() 方法募书,但是多數(shù)情況下你會使用 react-redux 提供的 connect() 幫助器來調(diào)用。bindActionCreators() 可以自動把多個 action 創(chuàng)建函數(shù) 綁定到 dispatch() 方法上测蹲。
Reducer
Reducers 指定了應(yīng)用狀態(tài)的變化如何響應(yīng) actions 并發(fā)送到 store 的莹捡,記住 actions 只是描述了有事情發(fā)生了這一事實(shí),并沒有描述應(yīng)用如何更新扣甲。 Reducers 就是一個純函數(shù)篮赢,接收舊的 state 和 action齿椅,返回新的 state。
Action 處理
(previousState, action) => newState
Reducers 處理
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter,
})
default:
return state
}
}
注意:
1.不要修改state启泣。 使用 Object.assign() 新建了一個副本涣脚。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter }),因?yàn)樗鼤淖兊谝粋€參數(shù)的值寥茫。你必須把第一個參數(shù)設(shè)置為空對象遣蚀。你也可以開啟對 ES7 提案對象展開運(yùn)算符的支持, 從而使用 { ...state, ...newState } 達(dá)到相同的目的。
2.在 default 情況下返回舊的 state纱耻。遇到未知的 action 時芭梯,一定要返回舊的 state。
reducer 可以拆分成多個來管理全局中的 state弄喘,每個 reducer 的 state 參數(shù)都不同玖喘,分別對應(yīng)它管理的那部分 state 數(shù)據(jù)。也可以放到不同的文件中限次,以保持其獨(dú)立性并專門處理不同的數(shù)據(jù)域芒涡。
最后,Redux 提供了 combineReducers() 工具類來做上面 todoApp 做的事情卖漫,這樣就能消滅一些樣板代碼了费尽。有了它,可以這樣重構(gòu) todoApp:
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action),
}
}
Store
我們學(xué)會了使用 action 來描述“發(fā)生了什么”羊始,和使用 reducers 來根據(jù) action 更新 state 的用法旱幼。
Store 就是把它們聯(lián)系到一起的對象。Store 有以下職責(zé):
- 維持應(yīng)用的 state突委;
- 提供 getState() 方法獲取 state柏卤;
- 提供 dispatch(action) 方法更新 state;
- 通過 subscribe(listener) 注冊監(jiān)聽器;
- 通過 subscribe(listener) 返回的函數(shù)注銷監(jiān)聽器匀油。
- 再次強(qiáng)調(diào)一下 Redux 應(yīng)用只有一個單一的 store缘缚。當(dāng)需要拆分?jǐn)?shù)據(jù)處理邏輯時,你應(yīng)該使用 reducer 組合 而不是創(chuàng)建多個 store敌蚜。
根據(jù)已有的 reducer 來創(chuàng)建 store 是非常容易的桥滨,我們使用 combineReducers() 將多個 reducer 合并成為一個。現(xiàn)在我們將其導(dǎo)入弛车,并傳遞 createStore()齐媒。
import { createStore } from "redux"
import todoApp from "./reducers"
let store = createStore(todoApp)
createStore() 的第二個參數(shù)是可選的, 用于設(shè)置 state 初始狀態(tài)。這對開發(fā)同構(gòu)應(yīng)用時非常有用纷跛,服務(wù)器端 redux 應(yīng)用的 state 結(jié)構(gòu)可以與客戶端保持一致, 那么客戶端可以將從網(wǎng)絡(luò)接收到的服務(wù)端 state 直接用于本地?cái)?shù)據(jù)初始化喻括。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
數(shù)據(jù)流
嚴(yán)格的單向數(shù)據(jù)流是 Redux 架構(gòu)的設(shè)計(jì)核心。
這意味著應(yīng)用中所有的數(shù)據(jù)都遵循相同的生命周期贫奠,這樣可以讓應(yīng)用變得更加可預(yù)測且容易理解唬血。同時也鼓勵做數(shù)據(jù)范式化望蜡,這樣可以避免使用多個且獨(dú)立的無法相互引用的重復(fù)數(shù)據(jù)。
Redux 應(yīng)用中數(shù)據(jù)的生命周期遵循下面 4 個步驟:
1.調(diào)用 store.dispatch(action)
Action 就是一個描述“發(fā)生了什么”的普通對象刁品。比如:
{ type: 'LIKE_ARTICLE', articleId: 42 }
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
{ type: 'ADD_TODO', text: 'Read the Redux docs.' }
可以把 action 理解成新聞的摘要泣特。如 “瑪麗喜歡 42 號文章浩姥√羲妫” 或者 “任務(wù)列表里添加了'學(xué)習(xí) Redux 文檔'”。
你可以在任何地方調(diào)用 store.dispatch(action)勒叠,包括組件中兜挨、XHR 回調(diào)中、甚至定時器中眯分。
2.Redux store 調(diào)用傳入的 reducer 函數(shù)
Store 會把兩個參數(shù)傳入 reducer: 當(dāng)前的 state 樹和 action拌汇。例如,在這個 todo 應(yīng)用中弊决,根 reducer 可能接收這樣的數(shù)據(jù):
// 當(dāng)前應(yīng)用的 state(todos 列表和選中的過濾器)
let previousState = {
visibleTodoFilter: "SHOW_ALL",
todos: [
{
text: "Read the docs.",
complete: false,
},
],
}
// 將要執(zhí)行的 action(添加一個 todo)
let action = {
type: "ADD_TODO",
text: "Understand the flow.",
}
// reducer 返回處理后的應(yīng)用狀態(tài)
let nextState = todoApp(previousState, action)
注意 reducer 是純函數(shù)噪舀。它僅僅用于計(jì)算下一個 state。它應(yīng)該是完全可預(yù)測的:多次傳入相同的輸入必須產(chǎn)生相同的輸出飘诗。它不應(yīng)做有副作用的操作与倡,如 API 調(diào)用或路由跳轉(zhuǎn)。這些應(yīng)該在 dispatch action 前發(fā)生昆稿。
3.根 reducer 應(yīng)該把多個子 reducer 輸出合并成一個單一的 state 樹
根 reducer 的結(jié)構(gòu)完全由你決定纺座。Redux 原生提供 combineReducers()輔助函數(shù),來把根 reducer 拆分成多個函數(shù)溉潭,用于分別處理 state 樹的一個分支净响。
下面演示 combineReducers() 如何使用。假如你有兩個 reducer:一個是 todo 列表喳瓣,另一個是當(dāng)前選擇的過濾器設(shè)置:
function todos(state = [], action) {
// 省略處理邏輯...
return nextState
}
function visibleTodoFilter(state = "SHOW_ALL", action) {
// 省略處理邏輯...
return nextState
}
let todoApp = combineReducers({
todos,
visibleTodoFilter,
})
當(dāng)你觸發(fā) action 后馋贤,combineReducers 返回的 todoApp 會負(fù)責(zé)調(diào)用兩個 reducer:
let nextTodos = todos(state.todos, action)
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)
然后會把兩個結(jié)果集合并成一個 state 樹:
return {
todos: nextTodos,
visibleTodoFilter: nextVisibleTodoFilter,
}
雖然 combineReducers() 是一個很方便的輔助工具,你也可以選擇不用畏陕;你可以自行實(shí)現(xiàn)自己的根 reducer配乓!
4.Redux store 保存了根 reducer 返回的完整 state 樹
這個新的樹就是應(yīng)用的下一個 state!所有訂閱 store.subscribe(listener) 的監(jiān)聽器都將被調(diào)用蹭秋;監(jiān)聽器里可以調(diào)用 store.getState() 獲得當(dāng)前 state扰付。
現(xiàn)在,可以應(yīng)用新的 state 來更新 UI仁讨。如果你使用了 React Redux 這類的綁定庫羽莺,這時就應(yīng)該調(diào)用 component.setState(newState) 來更新。
Redux 源碼解析
1.createStore 解析
import $$observable from "symbol-observable"
import ActionTypes from "./utils/actionTypes"
import isPlainObject from "./utils/isPlainObject"
export default function createStore(reducer, preloadedState, enhancer) {
// 如果 preloadedState和enhancer都為function洞豁,不支持盐固,throw new Error
// 我們都知道[initState]為object荒给, [enhancer]為function, typeof arguments[3] === 'function'
// 則拋出使用compose(),組合到一起, Store enhancer 是一個組合 store creator 的高階函數(shù),返回一個新的強(qiáng)化過的 store creator
if (
(typeof preloadedState === "function" && typeof enhancer === "function") ||
(typeof enhancer === "function" && typeof arguments[3] === "function")
) {
throw new Error(
"It looks like you are passing several store enhancers to " +
"createStore(). This is not supported. Instead, compose them " +
"together to a single function"
)
}
// preloadedState為function enhancer為undefined的時候說明initState沒有初始化, 但是有middleware
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
enhancer = preloadedState // 把 preloadedState 賦值給 enhancer
preloadedState = undefined // preloadedState賦值undeifined
}
// 如果參數(shù)enhancer存在
if (typeof enhancer !== "undefined") {
// 如果enhancer存在刁卜,那他必須是個function, 否則throw Error
if (typeof enhancer !== "function") {
throw new Error("Expected the enhancer to be a function.")
}
return enhancer(createStore)(reducer, preloadedState)
}
// 期待中的reducer是一個function
if (typeof reducer !== "function") {
throw new Error("Expected the reducer to be a function.")
}
let currentReducer = reducer // 臨時的reducer
let currentState = preloadedState // 臨時的 init state
let currentListeners = [] // 監(jiān)聽隊(duì)列和觀察者模式
let nextListeners = currentListeners // 淺拷貝這個隊(duì)列
let isDispatching = false // 我們很容易先假設(shè)isDispatching標(biāo)志是否正在執(zhí)行dispatch
// 先看下各個函數(shù)的名字志电, 打眼一看getState,dispatch蛔趴,subscribe都是比較熟悉的api
// subscribe挑辆,observable再加上定義的數(shù)組,應(yīng)該肯定是監(jiān)聽隊(duì)列和觀察者模式
// 其實(shí)這里是保存一份訂閱快照
function ensureCanMutateNextListeners() {
// 不要忘了let nextListeners = currentListeners // 淺拷貝下這個隊(duì)列
// 判斷nextListeners和當(dāng)前的currentListeners是不是一個引用
if (nextListeners === currentListeners) {
// 如果是一個引用的話深拷貝出來一個currentListeners賦值給nextListener
nextListeners = currentListeners.slice()
}
}
function getState() {
// dispatch中不可以getState, 為什么孝情?
// 因?yàn)閐ispatch是用來改變state的,為了確保state的正確性(獲取最新的state)鱼蝉,所有要判斷啦
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."
)
}
// 確定currentState是當(dāng)前的state 看 -> subscribe
return currentState
}
// store.subscribe方法設(shè)置監(jiān)聽函數(shù),一旦觸發(fā)dispatch箫荡,就自動執(zhí)行這個函數(shù)
// listener是一個callback function
function subscribe(listener) {
// 期望是個listener是個函數(shù)
if (typeof listener !== "function") {
throw new Error("Expected the listener to be a function.")
}
// 同理不可以dispatch中
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."
)
}
// 猜測是訂閱標(biāo)記, 用來標(biāo)記是否有l(wèi)istener
let isSubscribed = true
// 什么意思, 點(diǎn)擊進(jìn)去看看
ensureCanMutateNextListeners()
// push一個function魁亦,明顯的觀察者模式,添加一個訂閱函數(shù)
nextListeners.push(listener)
// 返回取消的function(unsubscribe)
return function unsubscribe() {
// 沒有l(wèi)istener直接返回
if (!isSubscribed) {
return
}
// 同理不可以dispatch中
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()
// 找到并刪除當(dāng)前的listener
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
// 發(fā)送一個action
function dispatch(action) {
// 看下util的isPlainObject
// acticon必須是由Object構(gòu)造的函數(shù)羔挡, 否則throw Error
if (!isPlainObject(action)) {
throw new Error(
"Actions must be plain objects. " +
"Use custom middleware for async actions."
)
}
// 判斷action, 不存在type throw Error
if (typeof action.type === "undefined") {
throw new Error(
'Actions may not have an undefined "type" property. ' +
"Have you misspelled a constant?"
)
}
// dispatch中不可以有進(jìn)行的dispatch
if (isDispatching) {
throw new Error("Reducers may not dispatch actions.")
}
try {
// 執(zhí)行時的標(biāo)記
isDispatching = true
// 執(zhí)行reducer洁奈, 來,回憶一下reducer绞灼,參數(shù)state利术, action 返回值newState
// 這就是dispatch一個action可以改變?nèi)謘tate的原因
currentState = currentReducer(currentState, action)
} finally {
// 最終執(zhí)行, isDispatching標(biāo)記為false镀赌, 即完成狀態(tài)
isDispatching = false
}
// 所有的的監(jiān)聽函數(shù)賦值給 listeners
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 執(zhí)行每一個監(jiān)聽函數(shù)
listener()
}
// 返回傳入的action
return action
}
// 到這里dispatch方法就結(jié)束了氯哮, 我們來思考總結(jié)一下, 為什么要用listeners
// 當(dāng)dispatch發(fā)送一個規(guī)范的action時商佛,會更新state
// 但是state改變了之后我們需要做一些事情喉钢, 比如更新ui既數(shù)據(jù)驅(qū)動視圖
// 所以要提供一個監(jiān)聽模式,當(dāng)然還要有一個監(jiān)聽函數(shù)subscribe, 保證dispatch和subscribe之間的一對多的模式
/* 替換store當(dāng)前使用的reducer函數(shù)
* 如果你的應(yīng)用程序?qū)崿F(xiàn)了代碼拆分并且你希望動態(tài)加載某些reducer的時候你
* 可能會用到這個方法良姆〕λ洌或者當(dāng)你要為Redux實(shí)現(xiàn)一個熱加載機(jī)制的時候,你也
* 會用到它
*/
function replaceReducer(nextReducer) {
// 期望nextReducer是個function
if (typeof nextReducer !== "function") {
throw new Error("Expected the nextReducer to be a function.")
}
// 當(dāng)前的currentReducer更新為參數(shù)nextReducer
currentReducer = nextReducer
// 發(fā)送一個dispatch初始化state玛追,表明一下是REPLACE
dispatch({ type: ActionTypes.REPLACE })
}
function observable() {
// 首先保留對Redux中subscribe方法的引用税课,在observable的世界里
const outerSubscribe = subscribe
return {
/**
* 一個極簡的observable訂閱方法。
* @param {Object} observer 任何可以作為observer使用的對象
* observer對象應(yīng)該包含一個`next`方法痊剖。
* @returns {subscription} 返回一個帶有`unsbscribe`方法的對象韩玩。該
* 方法將用于停止接收來自store的狀態(tài)變更信息。
*/
subscribe(observer) {
// 參數(shù)為object
if (typeof observer !== "object" || observer === null) {
throw new TypeError("Expected the observer to be an object.")
}
// 創(chuàng)建一個狀態(tài)變更回調(diào)函數(shù)陆馁。邏輯很簡單找颓,把store最新的狀態(tài)傳給observer
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
// 立即執(zhí)行一次回調(diào)函數(shù),把當(dāng)前狀態(tài)傳給observer
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
// 根據(jù)observable提案叮贩,[Symbol.observable]()返回observable對象自身
[$$observable]() {
return this
},
}
}
// dispatch初始化state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
}
}
2.applyMiddleware 解析
import compose from "./compose"
export default function applyMiddleware(...middlewares) {
// 返回名為createStore的函數(shù), 回應(yīng)createStore中的enhancer(createStore)(reducer, preloadedState)
return (createStore) => (...args) => {
// 保存createStore(reducer, initstate) || createStore(reducer), 賦值給store
const store = createStore(...args)
// 定義了一個dispatch击狮, 調(diào)用會 throw new Error(dispatching雖然構(gòu)造middleware但不允許其他middleware應(yīng)用)
// 作用是在dispatch改造完成前調(diào)用dispatch只會打印錯誤信息
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 定義middlewareAPI, 中間件中的store
const middlewareAPI = {
// add getState
getState: store.getState,
// 添加dispatch并包裝一個function佛析, 參數(shù)為(reducer, [initstate])
// 向下看一看middlewareAPI作為參數(shù)被回調(diào)回去,不難理解, 告訴dispath不能再middleware插件中構(gòu)造
dispatch: (...args) => dispatch(...args),
}
// 調(diào)用每一個這樣形式的middleware = store => next => action =>{},
// 組成一個這樣[f(next)=>acticon=>next(action)...]的array彪蓬,賦值給chain
// 調(diào)用數(shù)組中的每個中間件函數(shù)寸莫,得到所有的改造函數(shù)
/*假設(shè)有[a,b,c]三個middleware,他們都長這樣:
({ dispatch, getState }) => next => action => {
// 對action的操作
return next(action)
}
那么档冬,c會最先接收到一個參數(shù)膘茎,就是store.dispatch,作為它的next。然后c使用閉包將這個next存起來捣郊,
把自己作為下一個middlewareb的next參數(shù)傳入辽狈。這樣,就將所有的middleware串起來了呛牲。最后,
如果用戶dispatch一個action驮配,那么執(zhí)行順序會是: c --> b --> a
*/
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
// compose(...chain)會形成一個調(diào)用鏈, next指代下一個函數(shù)的注冊, 這就是中間件的返回值要是next(action)的原因
// 如果執(zhí)行到了最后next就是原生的store.dispatch方法
// 將這些改造函數(shù)compose成一個函數(shù)
// 用compose后的函數(shù)去改造store的dispatch
dispatch = compose(...chain)(store.dispatch)
// 返回增強(qiáng)的store, dispatch
return {
...store,
dispatch,
}
}
}
3.combineReducers 解析
import ActionTypes from "./utils/actionTypes"
import warning from "./utils/warning"
import isPlainObject from "./utils/isPlainObject"
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || "an action"
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? "preloadedState argument passed to createStore"
: "previous state received by the reducer"
if (reducerKeys.length === 0) {
return (
"Store does not have a valid reducer. Make sure the argument passed " +
"to combineReducers is an object whose values are reducers."
)
}
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
const unexpectedKeys = Object.keys(inputState).filter(
(key) => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach((key) => {
unexpectedKeyCache[key] = true
})
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? "keys" : "key"} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
// 很明顯assertReducerShape是用于reducer的規(guī)范
function assertReducerShape(reducers) {
Object.keys(reducers).forEach((key) => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === "undefined") {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION(),
}) === "undefined"
) {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
// 用于合并reducer 一般是這樣combineReducers({a,b,c})
export default function combineReducers(reducers) {
// reducers中key的數(shù)組
const reducerKeys = Object.keys(reducers)
// 最終的reducer
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}"`)
}
}
// reducer要是一個function
if (typeof reducers[key] === "function") {
// 賦值給finalReducers
finalReducers[key] = reducers[key]
}
}
// 符合規(guī)范的reducer的key數(shù)組
const finalReducerKeys = Object.keys(finalReducers)
// 意想不到的key娘扩, 先往下看看
let unexpectedKeyCache
if (process.env.NODE_ENV !== "production") {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
// 返回function, 即為createstore中的reducer參數(shù)既currentreducer
// 自然有state和action兩個參數(shù)壮锻, 可以回createstore文件看看currentReducer(currentState, action)
return function combination(state = {}, action) {
// reducer不規(guī)范報(bào)錯
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== "production") {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
// 狀態(tài)變化的標(biāo)志
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
// 獲取finalReducerKeys的key和value(function)
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 當(dāng)前key的state值
const previousStateForKey = state[key]
// 執(zhí)行reducer琐旁, 返回當(dāng)前state
const nextStateForKey = reducer(previousStateForKey, action)
// 不存在返回值報(bào)錯
if (typeof nextStateForKey === "undefined") {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// 新的state放在nextState對應(yīng)的key里
nextState[key] = nextStateForKey
// 判斷新的state是不是同一引用, 以檢驗(yàn)reducer是不是純函數(shù)
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
// 改變了返回nextState
return hasChanged ? nextState : state
}
}
4.bindActionCreator 解析
function bindActionCreator(actionCreator, dispatch) {
// 閉包
return function () {
// 執(zhí)行后返回結(jié)果為傳入的actionCreator直接調(diào)用arguments
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
// actionCreators為function
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"?`
)
}
// objec 為對象時, object 轉(zhuǎn)為數(shù)組
const keys = Object.keys(actionCreators)
// 定義return 的props
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
// actionCreators的key 通常為actionCreators function的name(方法名)
const key = keys[i]
// function => actionCreators工廠方法本身
const actionCreator = actionCreators[key]
if (typeof actionCreator === "function") {
// 判斷每個鍵在原始對象中的值是否是個函數(shù)猜绣,如果是一個函數(shù)則認(rèn)為它是一個動作工廠灰殴,
// 并使用bindActionCreator函數(shù)來封裝調(diào)度過程,最后把生成的新函數(shù)以同樣的鍵key存儲到boundActionCreators對象中掰邢。
// 在函數(shù)的末尾會返回boundActionCreators對象
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
// return 的props
return boundActionCreators
}
5.compose 解析
//Composes functions from right to left.
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)))
}
//like this
compose(funcA, funcB, funcC) === compose(funcA(funcB(funcC())))