国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

我的源碼閱讀之路:redux源碼剖析

CloudwiseAPM / 1205人閱讀

摘要:到月底了,小明的爸爸的單位發(fā)了工資總計(jì)塊大洋,拿到工資之后第一件的事情就是上交,毫無(wú)疑問(wèn)的,除非小明爸爸不要命了。當(dāng)小明的爸爸收到這個(gè)通知之后,心的一塊大石頭也就放下來(lái)了。下面我們正式開(kāi)始我們的源碼閱讀之旅。

前言

用過(guò)react的小伙伴對(duì)redux其實(shí)并不陌生,基本大多數(shù)的React應(yīng)用用到它。一般大家用redux的時(shí)候基本都不會(huì)多帶帶去使用它,而是配合react-redux一起去使用。剛學(xué)習(xí)redux的時(shí)候很容易弄混淆redux和react-redux,以為他倆是同一個(gè)東西。其實(shí)不然,redux是javascript應(yīng)用程序的可預(yù)測(cè)狀態(tài)容器,而react-redux則是用來(lái)連接這個(gè)狀態(tài)容器與react組件。可能前端新人對(duì)這兩者還是覺(jué)得很抽象,打個(gè)比方說(shuō),在一個(gè)普通家庭中,媽媽在家里都是至高無(wú)上的地位,掌握家中經(jīng)濟(jì)大權(quán),家里的經(jīng)濟(jì)流水都要經(jīng)過(guò)你的媽媽,而你的爸爸則負(fù)責(zé)從外面賺錢然后交給你的媽媽。這里把你的媽媽類比成redux,而你的爸爸可以類比成react-redux,而外面的大千世界則是react組件。相信這樣的類比,大家對(duì)這react和react-redux的有了一個(gè)初步認(rèn)識(shí)。本篇文章介紹的主要內(nèi)容是對(duì)redux的源碼的分析,react-redux的源碼分析將會(huì)在我的下一篇文章中,敬請(qǐng)期待!各位小伙們?nèi)绻X(jué)得寫(xiě)的不錯(cuò)的話,麻煩多多點(diǎn)贊收藏關(guān)注哦!

redux的使用

在講redux的源碼之前,我們先回顧一下redux是如何使用的,然后我們?cè)賹?duì)照著redux的使用去閱讀源碼,這樣大家的印象可能會(huì)更加深刻點(diǎn)。先貼上一段demo代碼:

const initialState={
  cash:200,

}
const reducer=(state=initialState,action)=>{
  const {type,payload} = action;
  switch(type){
    case "INCREMENT":
      return Object.assign({},state,{
        cash:state.cash+payload
      });
    case "DECREMENT":
      return Object.assign({},state,{
        cash:state.cash-payload
      });
    default :
      return state;
  }
}

const reducers=Redux.combineReducers({treasury:reducer});

//創(chuàng)建小金庫(kù)
const store=Redux.createStore(reducers);

//當(dāng)小金庫(kù)的現(xiàn)金發(fā)生變化時(shí),打印當(dāng)前的金額
store.subscribe(()=>{
  console.log(`余額:${store.getState().treasury.cash}`);
});

//小明爸爸發(fā)了工資300塊上交
store.dispatch({
  type:"INCREMENT",
  payload:300
});
//小明拿著水電費(fèi)單交100塊水電費(fèi)
store.dispatch({
  type:"DECREMENT",
  payload:100
});

上面這段代碼是一個(gè)非常典型的redux的使用,跟大家平時(shí)在項(xiàng)目里用的不太一樣,可能有些小伙伴們不能理解,其實(shí)react-redux只不過(guò)在這種使用方法上做了一層封裝。等當(dāng)我們弄清楚redux的使用,再去看react-redux源碼便會(huì)明白了我們?cè)陧?xiàng)目里為何是那種寫(xiě)法而不是這種寫(xiě)法。

說(shuō)到redux的使用,不免要說(shuō)一下action、reducer和store三者的關(guān)系。記得當(dāng)初第一次使用redux的時(shí)候,一直分不清這三者的關(guān)系,感覺(jué)這三個(gè)很抽象很玄學(xué),相信不少小伙伴們跟我一樣遇到過(guò)同樣的情況。其實(shí)并不難,我還是用文章開(kāi)頭打的比方還解釋這三者的關(guān)系。

現(xiàn)在保險(xiǎn)箱(store)里存放200塊大洋。到月底了,小明的爸爸的單位發(fā)了工資總計(jì)300塊大洋,拿到工資之后第一件的事情就是上交,毫無(wú)疑問(wèn)的,除非小明爸爸不要命了。小明的爸爸可以直接將這300塊大洋放到家里的保險(xiǎn)箱里面嗎?顯然是不可以的,所以小明的爸爸得向小明的爸爸提交申請(qǐng),而這個(gè)申請(qǐng)也就是我們所說(shuō)的action。這個(gè)申請(qǐng)(action)包括操作類型和對(duì)應(yīng)的東西,申請(qǐng)類型就是存錢(INCREMENT),對(duì)應(yīng)的東西就是300塊大洋(payload)。此時(shí)小明的媽媽拿到這個(gè)申請(qǐng)之后,將根據(jù)這個(gè)申請(qǐng)執(zhí)行對(duì)應(yīng)的操作,這里就是往保險(xiǎn)箱里的現(xiàn)金里放300塊大洋進(jìn)去,此時(shí)小明的媽媽干的事情就是reducer干的事情。當(dāng)300塊大洋放完之后,小明的媽媽就通知家里的所有人現(xiàn)在的小金庫(kù)的金額已經(jīng)發(fā)生了變化,現(xiàn)在的余額是500塊。當(dāng)小明的爸爸收到這個(gè)通知之后,心的一塊大石頭也就放下來(lái)了。過(guò)了一會(huì),小明回來(lái)了,并且拿著一張價(jià)值100塊的水電費(fèi)的催收單。于是,小明想小明媽媽申請(qǐng)交水電費(fèi),小明媽媽從保險(xiǎn)庫(kù)中取出來(lái)100塊給了小明,并通知了家里所有人小金庫(kù)的金額又發(fā)生了變化,現(xiàn)在余額400塊。

通過(guò)上面的例子,相信小伙們對(duì)三者的關(guān)系有了一個(gè)比較清晰的認(rèn)識(shí)。現(xiàn)在我們已經(jīng)理清楚了action、reducer和store三者的關(guān)系,并且也知道了redux是如何使用的了,現(xiàn)在將開(kāi)始我們得源碼閱讀之旅。

redux項(xiàng)目結(jié)構(gòu)

本篇文章是基于redux的4.0.0版本做的源碼分析,小伙伴們?cè)趯?duì)照源碼的時(shí)候,千萬(wàn)別弄錯(cuò)了。整個(gè)redux項(xiàng)目的源碼的閱讀我們只需要關(guān)注src的目錄即可。

這里主要分為兩大塊,一塊為自定義的工具庫(kù),另一塊則是redux的邏輯代碼。先從哪塊開(kāi)始閱讀呢?我個(gè)人建議先閱讀自定義的工具庫(kù)這塊。主要有這么兩個(gè)原因:第一個(gè),這塊代碼比較簡(jiǎn)單,容易理解,大家更能進(jìn)入閱讀的狀態(tài);第二個(gè),redux邏輯代碼會(huì)用到這些自定義工具,先搞懂這些,對(duì)后續(xù)邏輯代碼的閱讀做了一個(gè)很好的鋪墊。下面我們正式開(kāi)始我們的源碼閱讀之旅。

utils actionTypes.js
const ActionTypes = {
  INIT:
    "@@redux/INIT" +
    Math.random()
      .toString(36)
      .substring(7)
      .split("")
      .join("."),
  REPLACE:
    "@@redux/REPLACE" +
    Math.random()
      .toString(36)
      .substring(7)
      .split("")
      .join(".")
}

export default ActionTypes

這段代碼很好理解,就是對(duì)外暴露兩個(gè)action類型,沒(méi)什么難點(diǎn)。但是我這里想介紹的是Number.prototype.toString方法,估計(jì)應(yīng)該有不少小伙伴們不知道toString是可以傳參的,toString接收一個(gè)參數(shù)radix,代表數(shù)字的基數(shù),也就是我們所說(shuō)的2進(jìn)制、10進(jìn)制、16進(jìn)制等等。radix的取值范圍也很容易得出來(lái),最小進(jìn)制就是我們得二進(jìn)制,所以redix>=2。0-9(10個(gè)數(shù)字)+a-z(26個(gè)英文字母)總共36個(gè),所以redix<=36。總結(jié)一下2<=radix<=36,默認(rèn)是10。基于這個(gè)特性我們可以寫(xiě)一個(gè)獲取指定長(zhǎng)度的隨機(jī)字符串的長(zhǎng)度:

//獲取指定長(zhǎng)度的隨機(jī)字符串
function randomString(length){
  let str="";
  while(length>0){
    const fragment= Math.random().toString(36).substring(2);
    if(length>fragment.length){
      str+=fragment;
      length-=fragment.length;
    }else{
      str+=fragment.substring(0,length);
      length=0;
    }
  }
  return str;
}
isPlainObject.js
export default function isPlainObject(obj) {
  if (typeof obj !== "object" || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

isPlainObject.js也很簡(jiǎn)單,僅僅只是向外暴露了一個(gè)用于判斷是否簡(jiǎn)單對(duì)象的函數(shù)。什么簡(jiǎn)單對(duì)象?應(yīng)該有一些小伙伴不理解,所謂的簡(jiǎn)單對(duì)象就是該對(duì)象的__proto__等于Object.prototype,用一句通俗易懂的話就是:

凡不是new Object()或者字面量的方式構(gòu)建出來(lái)的對(duì)象都不是簡(jiǎn)單對(duì)象

下面看一個(gè)例子:

class Fruit{
  sayName(){
    console.log(this.name)
  }
}

class Apple extends Fruit{
  constructor(){
    super();
    this.name="蘋(píng)果"
  }
}

const apple = new Apple();
const fruit = new Fruit();
const cherry = new Object({
  name:"櫻桃"
});
const banana = {
  name:"香蕉"
};

console.log(isPlainObject(apple));//false
console.log(isPlainObject(fruit));//false
console.log(isPlainObject(cherry));//true
console.log(isPlainObject(banana));//true

這里可能會(huì)有人不理解isPlainObject(fruit)===false,如果對(duì)這個(gè)不能理解的話,自己后面要補(bǔ)習(xí)一下原型鏈的相關(guān)知識(shí),這里fruit.__proto__.__proto__才等價(jià)于Object.prototype。

warning.js
export default function warning(message) {
  if (typeof console !== "undefined" && typeof console.error === "function") {
    console.error(message)
  }
  try {
    throw new Error(message)
  } catch (e) {} 
}

這個(gè)也很簡(jiǎn)單,僅僅是打印一下錯(cuò)誤信息。不過(guò)這里它的console居然加了一層判斷,我查閱了一下發(fā)現(xiàn)console其實(shí)是有兼容性問(wèn)題,ie8及其以下都是不支持console的。哎,不僅感嘆一句!

如果說(shuō)馬賽克阻礙了人類文明的進(jìn)程,那ie便是阻礙了前端技術(shù)的發(fā)展。
邏輯代碼

到這里我已經(jīng)完成對(duì)utils下的js分析,很簡(jiǎn)單,并沒(méi)有大家想象的那么難。僅僅從這幾個(gè)簡(jiǎn)單的js中,就牽引出好幾個(gè)我們平時(shí)不太關(guān)注的知識(shí)點(diǎn)。如果我們不讀這些源碼,這些容易被忽視的知識(shí)點(diǎn)就很難被撿起來(lái),這也是為什么很多大佬建議閱讀源碼的原因。我個(gè)人認(rèn)為,閱讀源碼,理解原理是次要的。學(xué)習(xí)大佬的代碼風(fēng)格、一些解決思路以及對(duì)自己知識(shí)盲點(diǎn)的點(diǎn)亮更為重要。廢話不多說(shuō),開(kāi)始我們下一個(gè)部分的代碼閱讀,下面的部分就是整個(gè)redux的核心部分。

index.js
import createStore from "./createStore"
import combineReducers from "./combineReducers"
import bindActionCreators from "./bindActionCreators"
import applyMiddleware from "./applyMiddleware"
import compose from "./compose"
import warning from "./utils/warning"
import __DO_NOT_USE__ActionTypes from "./utils/actionTypes"

function isCrushed() {}

if (
  process.env.NODE_ENV !== "production" &&
  typeof isCrushed.name === "string" &&
  isCrushed.name !== "isCrushed"
) {
  warning(
    "You are currently using minified code outside of NODE_ENV === "production". " +
      "This means that you are running a slower development build of Redux. " +
      "You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify " +
      "or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) " +
      "to ensure you have the correct code for your production build."
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

index.js是整個(gè)redux的入口文件,尾部的export出來(lái)的方法是不是都很熟悉,每個(gè)方法對(duì)應(yīng)了一個(gè)js,這也是后面我們要分析的。這個(gè)有兩個(gè)點(diǎn)需要講一下:

第一個(gè),__DO_NOT_USE__ActionTypes。 這個(gè)很陌生,平時(shí)在項(xiàng)目里面我們是不太會(huì)用到的,redux的官方文檔也沒(méi)有提到這個(gè),如果你不看源碼你可能就不知道這個(gè)東西的存在。這個(gè)干嘛的呢?我們一點(diǎn)一點(diǎn)往上找,找到這么一行代碼:

import __DO_NOT_USE__ActionTypes from "./utils/actionTypes"

這個(gè)引入的js不就是我們之前分析的utils的其中一員嗎?里面定義了redux自帶的action的類型,從這個(gè)變量的命名來(lái)看,這是幫助開(kāi)發(fā)者檢查不要使用redux自帶的action的類型,以防出現(xiàn)錯(cuò)誤。

第二個(gè),函數(shù)isCrushed。 這里面定義了一個(gè)函數(shù)isCrushed,但是函數(shù)體里面并沒(méi)有東西。第一次看的時(shí)候很奇怪,為啥要這么干?相信有不少小伙伴們跟我有一樣的疑問(wèn),繼續(xù)往下看,緊跟著后面有一段代碼:

if (
  process.env.NODE_ENV !== "production" &&
  typeof isCrushed.name === "string" &&
  isCrushed.name !== "isCrushed"
) {
  warning(
    "You are currently using minified code outside of NODE_ENV === "production". " +
      "This means that you are running a slower development build of Redux. " +
      "You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify " +
      "or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) " +
      "to ensure you have the correct code for your production build."
  )
}

看到process.env.NODE_ENV,這里就要跟我們打包時(shí)用的環(huán)境變量聯(lián)系起來(lái)。當(dāng)process.env.NODE_ENV==="production"這句話直接不成立,所以warning也就不會(huì)執(zhí)行;當(dāng)process.env.NODE_ENV!=="production",比如是我們的開(kāi)發(fā)環(huán)境,我們不壓縮代碼的時(shí)候typeof isCrushed.name === "string" && isCrushed.name !== "isCrushed"也不會(huì)成立;當(dāng)process.env.NODE_ENV!=="production",同樣是我們的開(kāi)發(fā)環(huán)境,我們進(jìn)行了代碼壓縮,此時(shí)isCrushed.name === "string" && isCrushed.name !== "isCrushed"就成立了,可能有人不理解isCrushed函數(shù)不是在的嗎?為啥這句話就不成立了呢?其實(shí)很好理解,了解過(guò)代碼壓縮的原理的人都知道,函數(shù)isCrushed的函數(shù)名將會(huì)被一個(gè)字母所替代,這里我們舉個(gè)例子,我將redux項(xiàng)目的在development環(huán)境下進(jìn)行了一次壓縮打包。代碼做了這么一層轉(zhuǎn)換:

未壓縮

function isCrushed() {}
if (
  process.env.NODE_ENV !== "production" &&
  typeof isCrushed.name === "string" &&
  isCrushed.name !== "isCrushed"
)

壓縮后

function d(){}"string"==typeof d.name&&"isCrushed"!==d.name

此時(shí)判斷條件就成立了,錯(cuò)誤信息就會(huì)打印出來(lái)。這個(gè)主要作用就是防止開(kāi)發(fā)者在開(kāi)發(fā)環(huán)境下對(duì)代碼進(jìn)行壓縮。開(kāi)發(fā)環(huán)境下壓縮代碼,不僅讓我們

createStore.js

函數(shù)createStore接受三個(gè)參數(shù)(reducer、preloadedState、enhancer),reducer和enhancer我們用的比較多,preloadedState用的比較少。第一個(gè)reducer很好理解,這里就不過(guò)多解釋了,第二個(gè)preloadedState,它代表著初始狀態(tài),我們平時(shí)在項(xiàng)目里也很少用到它,主要說(shuō)一下enhancer,中文名叫增強(qiáng)器,顧名思義就是來(lái)增強(qiáng)redux的,它的類型的是Function,createStore.js里有這么一行代碼:

 if (typeof enhancer !== "undefined") {
    if (typeof enhancer !== "function") {
      throw new Error("Expected the enhancer to be a function.")
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

這行代碼展示了enhancer的調(diào)用過(guò)程,根據(jù)這個(gè)調(diào)用過(guò)程我們可以推導(dǎo)出enhancer的函數(shù)體的架子應(yīng)該是這樣子的:

 function enhancer(createStore) {
    return (reducer,preloadedState) => {
         //邏輯代碼
        .......
    }
 }

常見(jiàn)的enhancer就是redux-thunk以及redux-saga,一般都會(huì)配合applyMiddleware一起使用,而applyMiddleware的作用就是將這些enhancer格式化成符合redux要求的enhancer。具體applyMiddleware實(shí)現(xiàn),下面我們將會(huì)講到。我們先看redux-thunk的使用的例子:

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers/index";

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

看完上面的代碼,可能會(huì)有人有這么一個(gè)疑問(wèn)“createStore函數(shù)第二個(gè)參數(shù)不是preloadedState嗎?這樣不會(huì)報(bào)錯(cuò)嗎?” 首先肯定不會(huì)報(bào)錯(cuò),畢竟官方給的例子,不然寫(xiě)個(gè)錯(cuò)誤的例子也太大跌眼鏡了吧!redux肯定是做了這么一層轉(zhuǎn)換,我在createStore.js找到了這么一行代碼:

 if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
    enhancer = preloadedState
    preloadedState = undefined
  }

當(dāng)?shù)诙€(gè)參數(shù)preloadedState的類型是Function的時(shí)候,并且第三個(gè)參數(shù)enhancer未定義的時(shí)候,此時(shí)preloadedState將會(huì)被賦值給enhancer,preloadedState會(huì)替代enhancer變成undefined的。有了這么一層轉(zhuǎn)換之后,我們就可以大膽地第二個(gè)參數(shù)傳enhancer了。

說(shuō)完createStore的參數(shù),下面我說(shuō)一下函數(shù)createStore執(zhí)行完之后返回的對(duì)象都有什么?在createStore.js最下面一行有這一行代碼:

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

他返回了有這么幾個(gè)方法,其中前三個(gè)最為常用,后面兩個(gè)在項(xiàng)目基本上不怎么用,接下來(lái)我們?nèi)ヒ灰黄饰觥?/p> 定義的一些變量

let currentState = preloadedState //從函數(shù)createStore第二個(gè)參數(shù)preloadedState獲得
let currentReducer = reducer  //從函數(shù)createStore第一個(gè)參數(shù)reducer獲得
let currentListeners = [] //當(dāng)前訂閱者列表
let nextListeners = currentListeners //新的訂閱者列表
let isDispatching = false

其中變量isDispatching,作為鎖來(lái)用,我們r(jià)edux是一個(gè)統(tǒng)一管理狀態(tài)容器,它要保證數(shù)據(jù)的一致性,所以同一個(gè)時(shí)間里,只能做一次數(shù)據(jù)修改,如果兩個(gè)action同時(shí)觸發(fā)reducer對(duì)同一數(shù)據(jù)的修改,那么將會(huì)帶來(lái)巨大的災(zāi)難。所以變量isDispatching就是為了防止這一點(diǎn)而存在的。

dispatch
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
    }

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

函數(shù)dispatch在函數(shù)體一開(kāi)始就進(jìn)行了三次條件判斷,分別是以下三個(gè):

判斷action是否為簡(jiǎn)單對(duì)象

判斷action.type是否存在

判斷當(dāng)前是否有執(zhí)行其他的reducer操作

當(dāng)前三個(gè)預(yù)置條件判斷都成立時(shí),才會(huì)執(zhí)行后續(xù)操作,否則拋出異常。在執(zhí)行reducer的操作的時(shí)候用到了try-finally,可能大家平時(shí)try-catch用的比較多,這個(gè)用到的還是比較少。執(zhí)行前isDispatching設(shè)置為true,阻止后續(xù)的action進(jìn)來(lái)觸發(fā)reducer操作,得到的state值賦值給currentState,完成之后再finally里將isDispatching再改為false,允許后續(xù)的action進(jìn)來(lái)觸發(fā)reducer操作。接著一一通知訂閱者做數(shù)據(jù)更新,不傳入任何參數(shù)。最后返回當(dāng)前的action。

getState
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
  }

getState相比較dispatch要簡(jiǎn)單許多,返回currentState即可,而這個(gè)currentState在每次dispatch得時(shí)候都會(huì)得到響應(yīng)的更新。同樣是為了保證數(shù)據(jù)的一致性,當(dāng)在reducer操作的時(shí)候,是不可以讀取當(dāng)前的state值的。說(shuō)到這里,我想到之前一次的面試經(jīng)歷:

面試官:執(zhí)行createStore函數(shù)生成的store,可不可以直接修改它的state?

我:可以。(普羅大眾的第一反應(yīng))

面試官:你知道redux怎么做到不能修改store的state嗎?

我:額......(處于懵逼狀態(tài))

面試官:很簡(jiǎn)單啊!重寫(xiě)store的set方法啊!

那會(huì)沒(méi)看過(guò)redux的源碼,就被他忽悠了!讀完redux源碼之后,靠!這家伙就是個(gè)騙子!自己沒(méi)讀過(guò)源碼還跟我聊源碼,無(wú)語(yǔ)了!當(dāng)然,我自己也有原因,學(xué)藝不精,被忽悠了。我們這里看了源碼之后,getState函數(shù)返回state的時(shí)候,并沒(méi)有對(duì)currentState做一層拷貝再給我們,所以是可以直接修改的。只是這么修改的話,就不會(huì)通知訂閱者做數(shù)據(jù)更新。得出的結(jié)論是:

store通過(guò)getState得出的state是可以直接被更改的,但是redux不允許這么做,因?yàn)檫@樣不會(huì)通知訂閱者更新數(shù)據(jù)。
subscribe
function subscribe(listener) {
    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 //表示該訂閱者在訂閱狀態(tài)中,true-訂閱中,false-取消訂閱

    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)
    }
  }

在注冊(cè)訂閱者之前,做了兩個(gè)條件判斷:

判斷監(jiān)聽(tīng)者是否為函數(shù)

是否有reducer正在進(jìn)行數(shù)據(jù)修改(保證數(shù)據(jù)的一致性)

接下來(lái)執(zhí)行了函數(shù)ensureCanMutateNextListeners,下面我們看一下ensureCanMutateNextListeners函數(shù)的具體實(shí)現(xiàn)邏輯:

 function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

邏輯很簡(jiǎn)單,判斷nextListeners和currentListeners是否為同一個(gè)引用,還記得dispatch函數(shù)中有這么一句代碼以及定義變量時(shí)一行代碼嗎?

// Function dispatch
const listeners = (currentListeners = nextListeners)
// 定義變量
let currentListeners = []
let nextListeners = currentListeners

這兩處將nextListeners和currentListeners引用了同一個(gè)數(shù)組,另外定義變量時(shí)也有這么一句話代碼。而ensureCanMutateNextListeners就是用來(lái)判斷這種情況的,當(dāng)nextListeners和currentListeners為同一個(gè)引用時(shí),則做一層淺拷貝,這里用的就是Array.prototype.slice方法,該方法會(huì)返回一個(gè)新的數(shù)組,這樣就可以達(dá)到淺拷貝的效果。

函數(shù)ensureCanMutateNextListeners作為處理之后,將新的訂閱者加入nextListeners中,并且返回取消訂閱的函數(shù)unsubscribe。函數(shù)unsubscribe執(zhí)行時(shí),也會(huì)執(zhí)行兩個(gè)條件判斷:

是否已經(jīng)取消訂閱(已取消的不必執(zhí)行)

是否有reducer正在進(jìn)行數(shù)據(jù)修改(保證數(shù)據(jù)的一致性)

通過(guò)條件判斷之后,講該訂閱者從nextListeners中刪除。看到這里可能有小伙伴們對(duì)currentListeners和nextListeners有這么一個(gè)疑問(wèn)?函數(shù)dispatch里面將二者合并成一個(gè)引用,為啥這里有啥給他倆分開(kāi)?直接用currentListeners不可以嗎?這里這樣做其實(shí)也是為了數(shù)據(jù)的一致性,因?yàn)橛羞@么一種的情況存在。當(dāng)redux在通知所有訂閱者的時(shí)候,此時(shí)又有一個(gè)新的訂閱者加進(jìn)來(lái)了。如果只用currentListeners的話,當(dāng)新的訂閱者插進(jìn)來(lái)的時(shí)候,就會(huì)打亂原有的順序,從而引發(fā)一些嚴(yán)重的問(wèn)題。

replaceReducer
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Expected the nextReducer to be a function.")
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }

這個(gè)函數(shù)是用來(lái)替換reducer的,平時(shí)項(xiàng)目里基本很難用到,replaceReducer函數(shù)執(zhí)行前會(huì)做一個(gè)條件判斷:

判斷所傳reducer是否為函數(shù)

通過(guò)條件判斷之后,將nextReducer賦值給currentReducer,以達(dá)到替換reducer效果,并觸發(fā)state更新操作。

observable
  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */

這里沒(méi)貼代碼,因?yàn)檫@塊代碼我們不需要掌握。這個(gè)observable函數(shù),并沒(méi)有調(diào)用,即便暴露出來(lái)我們也辦法使用。所以我們就跳過(guò)這塊,如果有興趣的話,可以去作者給的github的地址了解一下。

講完這幾個(gè)方法之后,還有一個(gè)小細(xì)節(jié)需要說(shuō)一下,createStore函數(shù)體里有這樣一行代碼。

dispatch({ type: ActionTypes.INIT })

為啥要有這么一行代碼?原因很簡(jiǎn)單,假設(shè)我們沒(méi)有這樣代碼,此時(shí)currentState就是undefined的,也就我說(shuō)我們沒(méi)有默認(rèn)值了,當(dāng)我們dispatch一個(gè)action的時(shí)候,就無(wú)法在currentState基礎(chǔ)上做更新。所以需要拿到所有reducer默認(rèn)的state,這樣后續(xù)的dispatch一個(gè)action的時(shí)候,才可以更新我們的state。

combineReducers.js

這個(gè)js對(duì)應(yīng)著redux里的combineReducers方法,主要作用就是合并多個(gè)reducer。現(xiàn)在我們先給一個(gè)空的函數(shù),然后再一步步地根據(jù)還原源碼,這樣大家可能理解得更為透徹點(diǎn)。

//reducers  Object類型  每個(gè)屬性對(duì)應(yīng)的值都要是function
export default function combineReducers(reducers) {
    ....
}
第一步:淺拷貝reducers
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)
}

這里定義了一個(gè)finalReducers和finalReducerKeys,分別用來(lái)拷貝reducers和其屬性。先用Object.keys方法拿到reducers所有的屬性,然后進(jìn)行for循環(huán),每一項(xiàng)可根據(jù)其屬性拿到對(duì)應(yīng)的reducer,并淺拷貝到finalReducers中,但是前提條件是每個(gè)reducer的類型必須是Function,不然會(huì)直接跳過(guò)不拷貝。

第二步:檢測(cè)finalReducers里的每個(gè)reducer是否都有默認(rèn)返回值
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.`
      )
    }

    const type =
      "@@redux/PROBE_UNKNOWN_ACTION_" +
      Math.random()
        .toString(36)
        .substring(7)
        .split("")
        .join(".")
    if (typeof reducer(undefined, { type }) === "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.`
      )
    }
  })
}

export default function combineReducers(reducers) {
    //省略第一步的代碼
    ......
    let shapeAssertionError
    try {
        assertReducerShape(finalReducers)
    } catch (e) {
        shapeAssertionError = e
    }
}

assertReducerShape方法主要檢測(cè)兩點(diǎn):

不能占用的命名空間

如果遇到未知的action的類型,不需要要用默認(rèn)返回值

如果傳入type為 @@redux/INIT<隨機(jī)值> 的action,返回undefined,說(shuō)明沒(méi)有對(duì)未
知的action的類型做響應(yīng),需要加默認(rèn)值。如果對(duì)應(yīng)type為 @@redux/INIT<隨機(jī)值> 的action返回不為undefined,但是卻對(duì)應(yīng)type為 @@redux/PROBE_UNKNOWN_ACTION_<隨機(jī)值> 返回為undefined,說(shuō)明占用了 命名空間。整個(gè)邏輯相對(duì)簡(jiǎn)單,好好自己梳理一下。

第三步:返回一個(gè)函數(shù),用于代理所有的reducer
export default function combineReducers(reducers) {
    //省略第一步和第二步的代碼
    ......
    let unexpectedKeyCache
        if (process.env.NODE_ENV !== "production") {
        unexpectedKeyCache = {}
    }
    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
    }    
}

首先對(duì)傳入的state用getUnexpectedStateShapeWarningMessage做了一個(gè)異常檢測(cè),找出state里面沒(méi)有對(duì)應(yīng)reducer的key,并提示開(kāi)發(fā)者做調(diào)整。接著我們跳到getUnexpectedStateShapeWarningMessage里,看其實(shí)現(xiàn)。

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.`
    )
  }
}

getUnexpectedStateShapeWarningMessage接收四個(gè)參數(shù) inputState(state)、reducers(finalReducers)、action(action)、unexpectedKeyCache(unexpectedKeyCache),這里要說(shuō)一下unexpectedKeyCache是上一次檢測(cè)inputState得到的其里面沒(méi)有對(duì)應(yīng)的reducer集合里的異常key的集合。整個(gè)邏輯如下:

前置條件判斷,保證reducers集合不為{}以及inputState為簡(jiǎn)單對(duì)象

找出inputState里有的key但是 reducers集合里沒(méi)有key

如果是替換reducer的action,跳過(guò)第四步,不打印異常信息

將所有異常的key打印出來(lái)

getUnexpectedStateShapeWarningMessage分析完之后,我們接著看后面的代碼。

    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

首先定義了一個(gè)hasChanged變量用來(lái)表示state是否發(fā)生變化,遍歷reducers集合,將每個(gè)reducer對(duì)應(yīng)的原state傳入其中,得出其對(duì)應(yīng)的新的state。緊接著后面對(duì)新的state做了一層未定義的校驗(yàn),函數(shù)getUndefinedStateErrorMessage的代碼如下:

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.`
  )
}

邏輯很簡(jiǎn)單,僅僅做了一下錯(cuò)誤信息的拼接。未定義校驗(yàn)完了之后,會(huì)跟原state作對(duì)比,得出其是否發(fā)生變化。最后發(fā)生變化返回nextState,否則返回state。

compose.js

這個(gè)函數(shù)主要作用就是將多個(gè)函數(shù)連接起來(lái),將一個(gè)函數(shù)的返回值作為另一個(gè)函數(shù)的傳參進(jìn)行計(jì)算,得出最終的返回值。以烹飪?yōu)槔康搅侠矶际菑淖畛醯氖巢慕?jīng)過(guò)一道又一道的工序處理才得到的。compose的用處就可以將這些烹飪工序連接到一起,你只需要提供食材,它會(huì)自動(dòng)幫你經(jīng)過(guò)一道又一道的工序處理,烹飪出這道料理。

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)))
}

上面是es6的代碼,可能小伙伴們并不是很好理解,為了方便大家理解,我將其轉(zhuǎn)換成es5代碼去做講解。

function compose() {
  var _len = arguments.length;
  var funcs = [];
  for (var i = 0; i < _len; i++) {
    funcs[i] = arguments[i];
  }

  if (funcs.length === 0) {
    return function (arg) {
      return arg;
    };
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });
}

梳理一下整個(gè)流程,大致分為這么幾步:

新建一個(gè)新數(shù)組funcs,將arguments里面的每一項(xiàng)一一拷貝到funcs中去

當(dāng)funcs的長(zhǎng)度為0時(shí),返回一個(gè)傳入什么就返回什么的函數(shù)

當(dāng)funcs的長(zhǎng)度為1時(shí),返回funcs第0項(xiàng)對(duì)應(yīng)的函數(shù)

當(dāng)funcs的長(zhǎng)度大于1時(shí),調(diào)用Array.prototype.reduce方法進(jìn)行整合

這里我們正好復(fù)習(xí)一下數(shù)組的reduce方法,函數(shù)reduce接受下面四個(gè)參數(shù)

total 初始值或者計(jì)算得出的返回值

current 當(dāng)前元素

index 當(dāng)前元素的下標(biāo)

array 當(dāng)前元素所在的數(shù)組

示例:

const array = [1,2,3,4,5,6,7,8,9,10];
const totalValue=array.reduce((total,current)=>{
  return total+current
}); //55

這里的compose有個(gè)特點(diǎn),他不是從左到右執(zhí)行的,而是從右到左執(zhí)行的,下面我們看個(gè)例子:

const value=compose(function(value){
  return value+1;
},function(value){
  return value*2;
},function(value){
  return value-3;
})(2);
console.log(value);//(2-3)*2+1=-1

如果想要其從左向右執(zhí)行也很簡(jiǎn)單,做一下順序的顛倒即可。

===> 轉(zhuǎn)換前 return a(b.apply(undefined, arguments));
===> 轉(zhuǎn)換后 return b(a.apply(undefined, arguments));
applyMiddleware.js
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
    }
  }
}

前面我們講enhancer的時(shí)候,提到過(guò)這個(gè)applyMiddleware,現(xiàn)在我們將二者的格式對(duì)比看一下。

// enhancer
 function enhancer(createStore) {
    return (reducer,preloadedState) => {
         //邏輯代碼
        .......
    }
 }
//applyMiddleware
function //applyMiddleware(...middlewares) {
    return createStore => (...args) => {
        //邏輯代碼
        ....... 
    }
 }

通過(guò)二者的對(duì)比,我們發(fā)現(xiàn)函數(shù)applyMiddleware的返回就是一個(gè)enhancer,下面我們?cè)倏雌渚唧w實(shí)現(xiàn)邏輯:

通過(guò)createStore方法創(chuàng)建出一個(gè)store

定一個(gè)dispatch,如果在中間件構(gòu)造過(guò)程中調(diào)用,拋出錯(cuò)誤提示

定義middlewareAPI,有兩個(gè)方法,一個(gè)是getState,另一個(gè)是dispatch,將其作為中間件調(diào)用的store的橋接

middlewares調(diào)用Array.prototype.map進(jìn)行改造,存放在chain

用compose整合chain數(shù)組,并賦值給dispatch

將新的dispatch替換原先的store.dispatch

看完整個(gè)過(guò)程可能小伙伴們還是一頭霧水,玄學(xué)的很!不過(guò)沒(méi)關(guān)系,我們以redux-thunk為例,模擬一下整個(gè)過(guò)程中,先把redux-thunk的源碼貼出來(lái):

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === "function") {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

哈哈哈!看完redux-thunk的源碼之后是不是很奔潰,幾千star的項(xiàng)目居然就幾行代碼,頓時(shí)三觀就毀了有木有?其實(shí)源碼沒(méi)有大家想象的那么復(fù)雜,不要一聽(tīng)源碼就慌。穩(wěn)住!我們能贏!根據(jù)redux-thunk的源碼,我們拿到的thunk應(yīng)該是這樣子的:

 const thunk = ({ dispatch, getState })=>{
    return next => action => {
        if (typeof action === "function") {
            return action(dispatch, getState);
        }
        return next(action);
    };
 }  

我們經(jīng)過(guò)applyMiddleware處理一下,到第四步的時(shí)候,chain數(shù)組應(yīng)該是這樣子的:

const newDispatch;
const middlewareAPI={
  getState:store.getState,
  dispatch: (...args) => newDispatch(...args)
}
const { dispatch, getState } = middlewareAPI;
const  fun1 = (next)=>{
  return action => {
    if (typeof action === "function") {
        return action(dispatch, getState);
    }
    return next(action);
  }
}
const chain = [fun1]

compose整合完chain數(shù)組之后得到的新的dispatch的應(yīng)該是這樣子:

const newDispatch;
const middlewareAPI={
  getState:store.getState,
  dispatch: (...args) => newDispatch(...args)
}
const { dispatch, getState } = middlewareAPI;
const next = store.dispatch;
newDispatch = action =>{
  if (typeof action === "function") {
    return action(dispatch, getState);
  }
  return next(action);
}

接下來(lái)我們可以結(jié)合redux-thunk的例子來(lái)模擬整個(gè)過(guò)程:

function makeASandwichWithSecretSauce(forPerson) {
  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize("The Sandwich Shop", forPerson, error))
    );
  };
}
// store.dispatch就等價(jià)于newDispatch
store.dispatch(makeASandwichWithSecretSauce("Me"))

====> 轉(zhuǎn)換
const forPerson = "Me";
const action = (dispatch)=>{
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize("The Sandwich Shop", forPerson, error))
    );
}
newDispatch()

===> typeof action === "function" 成立時(shí)

 ((dispatch)=>{
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize("The Sandwich Shop", forPerson, error))
    );
  })( (...args) => newDispatch(...args), getState)

====> 計(jì)算運(yùn)行結(jié)果
const forPerson = "Me";
const dispatch = (...args) => newDispatch(...args) ;
fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize("The Sandwich Shop", forPerson, error))
);
// 其中:
function fetchSecretSauce() {
  return fetch("https://www.google.com/search?q=secret+sauce");
}
function makeASandwich(forPerson, secretSauce) {
  return {
    type: "MAKE_SANDWICH",
    forPerson,
    secretSauce
  };
}

function apologize(fromPerson, toPerson, error) {
  return {
    type: "APOLOGIZE",
    fromPerson,
    toPerson,
    error
  };
}
====> 我們這里只計(jì)算Promise.resolve的結(jié)果,并且假設(shè)fetchSecretSauce返回值為"666",即sauce="666"

const forPerson = "Me";
const dispatch = (...args) => newDispatch(...args) ;
dispatch({
    type: "MAKE_SANDWICH",
    "Me",
    "666"
})
====> 為了方便對(duì)比,我們?cè)俅无D(zhuǎn)換一下

const action = {
    type: "MAKE_SANDWICH",
    "Me",
    "666"
};

const next = store.dispatch

const newDispatch = action =>{
  if (typeof action === "function") {
    return action(dispatch, getState);
  }
  return next(action);
}

newDispatch(action)

====> 最終結(jié)果
store.dispatch({
    type: "MAKE_SANDWICH",
    "Me",
    "666"
});

以上就是redux-thunk整個(gè)流程,第一次看肯能依舊會(huì)很懵,后面可以走一遍,推導(dǎo)一下加深自己的理解。

bindActionCreators.js
export default function bindActionCreators(actionCreators, dispatch) {
  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"?`
    )
  }

  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") {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

bindActionCreators針對(duì)于三種情況有三種返回值,下面我們根據(jù)每種情況的返回值去分析。(為了方便理解,我們選擇在無(wú)集成中間件的情況)

typeof actionCreators === "function"
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}
const actionFun=bindActionCreator(actionCreators, dispatch)

===> 整合一下

const fun1 = actionCreators;
const dispatch= stror.dispatch;
const actionFun=function () {
    return dispatch(fun1.apply(this, arguments))
 }

根據(jù)上面的推導(dǎo),當(dāng)變量actionCreators的類型為Function時(shí),actionCreators必須返回一個(gè)action。

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"?`
    )

提示開(kāi)發(fā)者actionCreators類型錯(cuò)誤,應(yīng)該是一個(gè)非空對(duì)象或者是函數(shù)。

默認(rèn)
 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") {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators

通過(guò)和第一種情況對(duì)比發(fā)現(xiàn),當(dāng)actionCreators的每一項(xiàng)都執(zhí)行一次第一種情況的操作。換句話說(shuō),默認(rèn)情況是第一種情況的集合。

以上是對(duì)bindActionCreators的剖析,可能小伙伴們對(duì)這個(gè)還是不夠理解,不過(guò)沒(méi)有關(guān)系,只要知道bindActionCreators干了啥就行。bindActionCreators是需要結(jié)合react-redux一起使用的,由于本篇文章沒(méi)有講解react-redux,所以這里我們不對(duì)bindActionCreators做更深入的講解。下篇文章講react-redux,會(huì)再次提到bindActionCreators。

結(jié)語(yǔ)

到這里整個(gè)redux的源碼我們已經(jīng)剖析完了,整個(gè)redux代碼量不是很大,但是里面的東西還是很多的,邏輯相對(duì)來(lái)說(shuō)有點(diǎn)繞。不過(guò)沒(méi)關(guān)系,沒(méi)有什么是看了好幾次都看不懂的,如果有那就再多看幾次嘛!另外再多一嘴,如果想快讀提高自己的小伙伴們,我個(gè)人是強(qiáng)烈推薦看源碼的。正所謂“近朱者赤,近墨者黑”,多看看大神的代碼,對(duì)自己的代碼書(shū)寫(xiě)、代碼邏輯、知識(shí)點(diǎn)查缺補(bǔ)漏等等方面都是很大幫助的。就拿我自己來(lái)說(shuō),我每次閱讀完一篇源碼之后,都受益匪淺。可能第一次看源碼,有著諸多的不適應(yīng),畢竟萬(wàn)事開(kāi)頭難,如果強(qiáng)迫自己完成第一次的源碼閱讀,那往后的源碼閱讀將會(huì)越來(lái)越輕松,對(duì)自己的提升也就越來(lái)越快。各位騷年們,擼起袖子加油干吧!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/97927.html

相關(guān)文章

  • Redis 哈希結(jié)構(gòu)內(nèi)存模型剖析

    摘要:本文共字,閱讀大約需要分鐘概述在前文字符串類型內(nèi)部編碼剖析之中已經(jīng)剖析過(guò)最基本的類型的內(nèi)部是怎么編碼和存儲(chǔ)的,本文再來(lái)闡述中使用最為頻繁的數(shù)據(jù)類型哈希或稱散列,在內(nèi)部是怎么存的。 showImg(https://segmentfault.com/img/remote/1460000016158153); 本文共 1231字,閱讀大約需要 5分鐘 ! 概述 在前文《Redis字符串類型...

    Salamander 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<