理解React中的Redux

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)

redux.png

三大原則

單一數(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())))
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牺陶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辣之,更是在濱河造成了極大的恐慌掰伸,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怀估,死亡現(xiàn)場離奇詭異狮鸭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)多搀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門歧蕉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人康铭,你說我怎么就攤上這事惯退。” “怎么了麻削?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵蒸痹,是天一觀的道長春弥。 經(jīng)常有香客問我,道長叠荠,這世上最難降的妖魔是什么匿沛? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮榛鼎,結(jié)果婚禮上逃呼,老公的妹妹穿的比我還像新娘。我一直安慰自己者娱,他們只是感情好抡笼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黄鳍,像睡著了一般推姻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上框沟,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天藏古,我揣著相機(jī)與錄音,去河邊找鬼忍燥。 笑死拧晕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梅垄。 我是一名探鬼主播厂捞,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼队丝!你這毒婦竟也來了靡馁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炭玫,失蹤者是張志新(化名)和其女友劉穎奈嘿,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吞加,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裙犹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了衔憨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叶圃。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖践图,靈堂內(nèi)的尸體忽然破棺而出掺冠,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布德崭,位于F島的核電站斥黑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏眉厨。R本人自食惡果不足惜锌奴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憾股。 院中可真熱鬧鹿蜀,春花似錦、人聲如沸服球。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斩熊。三九已至往枣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間座享,已是汗流浹背婉商。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渣叛,地道東北人。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓盯捌,卻偏偏與公主長得像淳衙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子饺著,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355