成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

redux源碼解讀--createStore源碼解析

tianren124 / 2592人閱讀

摘要:源碼解析是最核心的模塊。比如,當(dāng)我們需要使用中間件的時(shí)候,就會(huì)像第三個(gè)參數(shù)傳遞一個(gè)返回值是一個(gè)。后續(xù)的源碼解讀和測(cè)試?yán)涌梢躁P(guān)注源碼解讀倉庫

createStore源碼解析

createStoreredux最核心的模塊。這個(gè)模塊就是用于創(chuàng)建一個(gè)store對(duì)象,同時(shí),對(duì)外暴露出dispatch,getState,subscribereplaceReducer方法。(源碼中關(guān)于observable的部分可以忽略,這個(gè)是redux內(nèi)部使用的。我們?cè)陂_發(fā)中幾乎幾乎用不到)

先看一下這個(gè)模塊的基本結(jié)構(gòu):

依賴

lodash/isPlainObject

symbol-observable

對(duì)外輸出

dispatch

getState

subscribe

replaceReducer

[$$observable](幾乎不用)

redux源碼中使用的lodash/isPlainObject依賴。在IE6-8中性能很差,其實(shí)現(xiàn)方式和jQuery3.x的實(shí)現(xiàn)相似,在舊版本的IE中支持不了。最后會(huì)和大家一起探討。

源碼注釋

// 判斷是不是純粹對(duì)象的模塊({})
import isPlainObject from "lodash/isPlainObject"
// 引入observable支持
import $$observable from "symbol-observable"
export const ActionTypes = {
  INIT: "@@redux/INIT"
}

上面這個(gè)是redux內(nèi)部使用的一個(gè)action。主要用于內(nèi)部測(cè)試和渲染初始的state。記住,我們自己編寫action的時(shí)候,action.type不能為@@redux/INIT。因?yàn)椋@個(gè)action會(huì)在redux的內(nèi)部自動(dòng)調(diào)用。比如,下面的搗蛋代碼:

import {createStore, combineReducers, applyMiddleware} from "../src"
import logger from "redux-logger"

const actionTypes = "@@redux/INIT"
const reducers = (state = {}, action) => {
  switch(action.type) {
    case actionTypes:
      console.log("hello @@redux/INIT")
      return {
        "type": actionTypes
      }
    default:
      return state
  }
}
const store = createStore(reducers, applyMiddleware(logger))
console.log("*************************************")
console.log(store.getState()) //會(huì)渲染為 {"type": "@@redux/INIT"}
console.log("*************************************")

下面就是createStore方法的實(shí)現(xiàn):

export default function createStore(reducer, preloadedState, enhancer){
  //...初始條件的判斷和設(shè)定
  function getState() {
    // getState方法的實(shí)現(xiàn)
  }
  function subscribe() {
    // subscribe方法的實(shí)現(xiàn)
  }
  function dispatch() {
    // dispatch方法的實(shí)現(xiàn)
  }
  function replaceReducer() {
    // replaceReducer方法的實(shí)現(xiàn)
  }
  function observable() {
    // observable方法的實(shí)現(xiàn)
  }
  // store被創(chuàng)建后,自動(dòng)分發(fā)一個(gè)"INIT" action。渲染出初始化的state樹。
  dispatch({ type: ActionTypes.INIT })
}

下面就來一點(diǎn)點(diǎn)分析每一行代碼到底做什么:

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

在調(diào)用createStore的方法的時(shí)候,可以傳遞三個(gè)參數(shù)createStore(reducer, preloadedState, enhancer)。其中,各參數(shù)屬性如下:

reducer必需參數(shù),function類型

preloadedState可選參數(shù),object類型

enhancer可選參數(shù),function類型

在平常的使用中,我們一般會(huì)省略第二個(gè)參數(shù)。比如,當(dāng)我們需要使用redux中間件的時(shí)候,就會(huì)像第三個(gè)參數(shù)傳遞一個(gè)applyMiddleware()[返回值是一個(gè)function]。如果,我們沒有初始狀態(tài),則會(huì)省略第二個(gè)參數(shù)。這個(gè)時(shí)候,我們的函數(shù)調(diào)用形式為:

const store = createStore(reducer, applyMiddleware(...))

這個(gè)時(shí)候就會(huì)執(zhí)行上面源碼中的代碼,使函數(shù)調(diào)用滿足最基本的形式調(diào)用。也就是函數(shù)在傳遞兩個(gè)或者三個(gè)參數(shù)的情況下,其內(nèi)部處理邏輯都是一樣的。

// 如果我們指定了reducer增強(qiáng)器enhancer
if (typeof enhancer !== "undefined") {
  // enhancer必須是一個(gè)函數(shù)
  if (typeof enhancer !== "function") {
      throw new Error("Expected the enhancer to be a function.")
    }
    // 這個(gè)函數(shù)接收createStore作為參數(shù),并且返回一個(gè)函數(shù),這個(gè)函數(shù)接收的參數(shù)是reducer,preloadedState
    // 直接返回經(jīng)過enhancer包裝的對(duì)象
    return enhancer(createStore)(reducer, preloadedState)
  }

想更好的理解這段代碼,可以參考applyMiddleware內(nèi)部的實(shí)現(xiàn)。

// 要求傳遞給createStore的第一個(gè)參數(shù)必須是一個(gè)函數(shù)
if (typeof reducer !== "function") {
    throw new Error("Expected the reducer to be a function.")
 }
// 保存初始的reducer
let currentReducer = reducer
// 保存初始的state
let currentState = preloadedState
// 保存所有的事件監(jiān)聽器
let currentListeners = []
// 獲取當(dāng)前監(jiān)聽器的一個(gè)副本(相同的引用)
let nextListeners = currentListeners
// 是否正在派發(fā)action
let isDispatching = false

function ensureCanMutateNextListeners() {
  // 如果nextListeners和currentListeners具有相同的引用,則獲取一份當(dāng)前事件監(jiān)聽器集合的一個(gè)副本保存到nextListeners中
  if (nextListeners === currentListeners) {
    nextListeners = currentListeners.slice()
  }
}

上面就是createStore方法中的一些初始參數(shù),這里有一個(gè)地方值得思考:為什么要維護(hù)兩份事件監(jiān)聽器列表(nextListeners,currentListeners)?。下面,我們會(huì)解釋。

// 直接返回當(dāng)前store的state
function getState() {
  return currentState
}
  function subscribe(listener) {
    // 事件監(jiān)聽器必須是函數(shù),否則會(huì)拋出異常
    if (typeof listener !== "function") {
      throw new Error("Expected listener to be a function.")
    }
    // 這個(gè)事件監(jiān)聽器是否已經(jīng)被取消的標(biāo)志(個(gè)人感覺:這個(gè)初始值應(yīng)該被設(shè)置為false,語意化更好一些。)
    let isSubscribed = true
    // 調(diào)用這個(gè)函數(shù)的結(jié)果就是生成一份當(dāng)前事件監(jiān)聽器的一個(gè)副本保存到nextListeners中
    ensureCanMutateNextListeners()
    // 將新的事件監(jiān)聽器添加到nextListeners中
    nextListeners.push(listener)
    
    // 返回一個(gè)取消監(jiān)聽的函數(shù)
    return function unsubscribe() {
      // 如果這個(gè)監(jiān)聽器已經(jīng)被取消了,則直接return
      if (!isSubscribed) {
        return
      }
      // 將監(jiān)聽器是否取消的標(biāo)志設(shè)置為false
      isSubscribed = false
      // 再次生成一份事件監(jiān)聽器集合的副本
      ensureCanMutateNextListeners()
      // 獲取到需要取消的事件監(jiān)聽器的索引
      const index = nextListeners.indexOf(listener)
      // 從事件監(jiān)聽器集合中刪除這個(gè)事件監(jiān)聽器
      nextListeners.splice(index, 1)
    }
  }

subscribe方法的源碼中可以看出,每次在進(jìn)行監(jiān)聽器的添加/刪除之前,都會(huì)基于當(dāng)前的監(jiān)聽器集合生成一個(gè)副本保存到nextListeners中。這個(gè)時(shí)候還是不能準(zhǔn)確的回答上面的問題,下面我們繼續(xù)研究dispatch的源碼:

function dispatch(action) {
    // dispatch的參數(shù)就是我們需要派發(fā)的action,一定要保證這個(gè)action是一個(gè)純粹的對(duì)象
    // 如果不是一個(gè)純粹的對(duì)象,則會(huì)拋出異常。
    if (!isPlainObject(action)) {
      // 這個(gè)方法有坑,在低版本的IE瀏覽器中性能很差,最后我們會(huì)研究這個(gè)方法的源碼。
      throw new Error(
        "Actions must be plain objects. " +
        "Use custom middleware for async actions."
      )
    }

    // 所派發(fā)的action必須有一個(gè)type屬性(我們可以將這個(gè)屬性認(rèn)為就是action的身份證,這樣redux才知道你派發(fā)的是哪個(gè)action,你需要做什么,該怎么為你做)
    // 如果沒有這個(gè)屬性則會(huì)拋出異常
    if (typeof action.type === "undefined") {
      throw new Error(
        "Actions may not have an undefined "type" property. " +
        "Have you misspelled a constant?"
      )
    }
    
    // 如果redux正在派發(fā)action,則拋出異常?什么時(shí)候會(huì)出現(xiàn)這種情況???
    if (isDispatching) {
      throw new Error("Reducers may not dispatch actions.")
    }

    try {
      isDispatching = true
      // 派發(fā)action
      // 實(shí)質(zhì)就是將當(dāng)前的state和你需要派發(fā)的action傳遞給reducer函數(shù)病返回一個(gè)新的state
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 這一塊也是一個(gè)十分關(guān)鍵的地方,哈哈哈哈哈,又多了一份事件監(jiān)聽器的列表,簡(jiǎn)單的說一下這三份列表的作用
      // nextListeners: 保存這次dispatch后,需要觸發(fā)的所有事件監(jiān)聽器的列表
    // currentListeners: 保存一份nextListeners列表的副本
      // listeners: 需要執(zhí)行的列表
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      // 調(diào)用所有的事件監(jiān)聽器
      listener()
    }
    //  dispatch的返回值也是十分重要的,如果沒有這個(gè)返回值,就不可能引入強(qiáng)大的中間件機(jī)制。
    return action
  }

到這里,我們就可以回答這個(gè)問題了:為什么要維護(hù)兩份事件監(jiān)聽器列表(nextListeners,currentListeners)?

首先,我們必須要知道的事情就是:我們的監(jiān)聽器在什么時(shí)候會(huì)執(zhí)行?在我們的調(diào)用dispatch派發(fā)action之后。ok,看下面的這個(gè)圖:

這個(gè)圖表示,當(dāng)dispatch方法執(zhí)行到這行代碼的時(shí)候,listenerscurrentListeners,nextListeners這三個(gè)變量引用內(nèi)存中的同一份數(shù)組,只要其中一個(gè)發(fā)生變化,另外兩個(gè)立馬改變。和下面的這個(gè)例子一樣的含義:

所以,在這種情況下。如果我在某個(gè)事件監(jiān)聽器函數(shù)中調(diào)用了取消了某個(gè)監(jiān)聽器,那么在這次dispatch后,被取消的這個(gè)事件監(jiān)聽器就不會(huì)被執(zhí)行了(?????是嗎????)。

import {createStore, combineReducers, applyMiddleware} from "../src"
import logger from "redux-logger"

const actionTypes = "@@redux/INIT"
const reducers = (state = {}, action) => {
  switch(action.type) {
    case actionTypes:
      return {
        "type": actionTypes
      }
    default:
      return state
  }
}
const store = createStore(reducers, applyMiddleware(logger))

const listener1 = store.subscribe(() => {
    console.log("listener1")
})


const listener2 = store.subscribe(() => {
    // 取消listener3
    listener3()
    console.log("listener2")
})


const listener3 = store.subscribe(() => {
    console.log("listener3")
})

store.dispatch({type: actionTypes})

結(jié)果是:

listener1
listener2
listener3

結(jié)果,就是:即使你在某個(gè)事件監(jiān)聽器中,取消了其它的事件監(jiān)聽器,那么被取消的這個(gè)事件監(jiān)聽器,在這次dispatch后仍然會(huì)執(zhí)行。也就是說。redux會(huì)保證在某個(gè)dispatch后,會(huì)保證在這個(gè)dispatch之前的所有事件監(jiān)聽器全部執(zhí)行。

這是個(gè)bug還是個(gè)feature。無從而知,但是從redux源碼中,可以知道,這是一個(gè)bug。所以,redux作者就利用上面的方法很巧妙的避免了這種情況。其實(shí)實(shí)現(xiàn)的方法很簡(jiǎn)單:切斷nextListeners和currentListener,listeners相同的引用關(guān)系。

下面接著扯:

// 提換reducer的方法。(動(dòng)態(tài)加載reducers的時(shí)候才用)
function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Expected the nextReducer to be a function.")
    }

    currentReducer = nextReducer
    // 替換結(jié)束后,重新初始化
    dispatch({ type: ActionTypes.INIT })
  }
// 觸發(fā)預(yù)設(shè)action,主要就是為了生成初始的state tree的結(jié)構(gòu)
dispatch({ type: ActionTypes.INIT })
// 這就很熟悉了吧
return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
      // 尼瑪 忽略這個(gè)
    [$$observable]: observable
  }

這就是對(duì)createStore源碼的一個(gè)整體解讀,水平有限,歡迎拍磚。后續(xù)的源碼解讀和測(cè)試?yán)涌梢躁P(guān)注:redux源碼解讀倉庫

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/89454.html

相關(guān)文章

  • redux源碼解讀--applyMiddleware源碼解析

    摘要:的中間件主要是通過模塊實(shí)現(xiàn)的。返回的也是一個(gè)對(duì)象這個(gè)其實(shí)就是,各個(gè)中間件的最底層第三層的哪個(gè)函數(shù)組成的圓環(huán)函數(shù)構(gòu)成的這就是對(duì)源碼的一個(gè)整體解讀,水平有限,歡迎拍磚。后續(xù)的源碼解讀和測(cè)試?yán)涌梢躁P(guān)注源碼解讀倉庫 applyMiddleware源碼解析 中間件機(jī)制在redux中是強(qiáng)大且便捷的,利用redux的中間件我們能夠?qū)崿F(xiàn)日志記錄,異步調(diào)用等多種十分實(shí)用的功能。redux的中間件主要是...

    Atom 評(píng)論0 收藏0
  • Redux 入坑進(jìn)階 - 源碼解析

    摘要:否則的話,認(rèn)為只是一個(gè)普通的,將通過也就是進(jìn)一步分發(fā)。在本組件內(nèi)的應(yīng)用傳遞給子組件源碼解析期待一個(gè)作為傳入,里面是如果只是傳入一個(gè),則通過返回被綁定到的函數(shù)遍歷并通過分發(fā)綁定至將其聲明為的屬性之一接收的作為傳入。 原文鏈接:https://github.com/ecmadao/Co...轉(zhuǎn)載請(qǐng)注明出處 本文不涉及redux的使用方法,因此可能更適合使用過redux的玩家翻閱? 預(yù)熱...

    BothEyes1993 評(píng)論0 收藏0
  • 重讀redux源碼(二)

    摘要:函數(shù)組合,科里化的串聯(lián)結(jié)合示例源碼,實(shí)現(xiàn)也很優(yōu)雅,對(duì)于返回的,將等參數(shù)傳遞進(jìn)去,然后執(zhí)行,等待回調(diào)異步完成再。對(duì)于正常對(duì)象則進(jìn)行下一步。前言 作為前端狀態(tài)管理器,這個(gè)比較跨時(shí)代的工具庫redux有很多實(shí)現(xiàn)和思想值得我們思考。在深入源碼之前,我們可以相關(guān)注下一些常見問題,這樣帶著問題去看實(shí)現(xiàn),也能更加清晰的了解。 常見問題 大概看了下主要有這么幾個(gè): redux三大原則 這個(gè)可以直接參考...

    dingda 評(píng)論0 收藏0
  • 不一樣的redux源碼解讀

    摘要:這里還有一個(gè)疑問點(diǎn)就是的嵌套,最開始也我不明白,看了源碼才知道,這里返回的也是接受也就是一個(gè)所以可以正常嵌套。以作為參數(shù),調(diào)用上一步返回的函數(shù)以為參數(shù)進(jìn)行調(diào)用。 1、本文不涉及redux的使用方法,因此可能更適合使用過 redux 的同學(xué)閱讀2、當(dāng)前redux版本為4.0.13、更多系列文章請(qǐng)看 Redux作為大型React應(yīng)用狀態(tài)管理最常用的工具。雖然在平時(shí)的工作中很多次的用到了它...

    Salamander 評(píng)論0 收藏0
  • redux源碼閱讀--主模塊

    摘要:主模塊的入口模塊就是。主要就做兩件事引入個(gè)功能模塊,并掛載至同一個(gè)對(duì)象上,對(duì)外暴露。在非環(huán)境下壓縮代碼,給予警告。后續(xù)的源碼解讀和測(cè)試?yán)涌梢躁P(guān)注源碼解讀倉庫 主模塊 redux的入口模塊就是src/index.js。這個(gè)文件的代碼十分簡(jiǎn)單。主要就做兩件事: 引入個(gè)功能模塊,并掛載至同一個(gè)對(duì)象上,對(duì)外暴露。 在非production環(huán)境下壓縮代碼,給予警告。 下面是模塊的源碼(只包...

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

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

0條評(píng)論

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