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

資訊專欄INFORMATION COLUMN

重讀redux源碼(二)

dingda / 3183人閱讀

摘要:函數(shù)組合,科里化的串聯(lián)結(jié)合示例源碼,實(shí)現(xiàn)也很優(yōu)雅,對(duì)于返回的,將等參數(shù)傳遞進(jìn)去,然后執(zhí)行,等待回調(diào)異步完成再。對(duì)于正常對(duì)象則進(jìn)行下一步。

前言

作為前端狀態(tài)管理器,這個(gè)比較跨時(shí)代的工具庫(kù)redux有很多實(shí)現(xiàn)和思想值得我們思考。在深入源碼之前,我們可以相關(guān)注下一些常見(jiàn)問(wèn)題,這樣帶著問(wèn)題去看實(shí)現(xiàn),也能更加清晰的了解。

常見(jiàn)問(wèn)題

大概看了下主要有這么幾個(gè):

    redux三大原則
    這個(gè)可以直接參考官方文檔

    redux 的優(yōu)缺點(diǎn)。 關(guān)于優(yōu)缺點(diǎn),太主觀了大家見(jiàn)仁見(jiàn)智。

    redux中間件相關(guān),洋蔥模型是什么,常見(jiàn)中間件。

背景

有關(guān)acton,reducer相關(guān)的部分可以看我前面的文章。我們主要關(guān)注針對(duì)store和中間件相關(guān)的部分來(lái)解讀。

store的創(chuàng)建

作為維護(hù)和管理數(shù)據(jù)的部分,store在redux中的作用十分重要。在action發(fā)出指令,reduxer進(jìn)行數(shù)據(jù)更新之后,監(jiān)聽(tīng)數(shù)據(jù)變化和同步數(shù)據(jù)更新的操作都要借助store來(lái)實(shí)現(xiàn)。

createStore 輸入和輸出

首先看下createStore的使用,即常見(jiàn)的就是接受經(jīng)過(guò)combineReducers處理之后的reducer和初始的state

import reducer from "./reducers"
const store = createStore(reducer,initialState)

此外還可以接受第三個(gè)參數(shù)enhancer(增強(qiáng)器,一般就是applyMiddleware)

/**
 * 創(chuàng)建管理state 樹(shù)的Redux store
 * 應(yīng)用中只能存在一個(gè)store,為了區(qū)分不同action對(duì)應(yīng)的reducer,
 * 可以通過(guò)combineReducers來(lái)關(guān)聯(lián)不同的reducer
 * @param {Function} reducer   combineReducers關(guān)聯(lián)之后的reducer
 * @param {Object} preloadedState 初始state
 * @param {Function} enhancer 可以增強(qiáng)store功能的函數(shù),例如中間件等。唯一適合
 * @returns 返回一個(gè)Store 以維護(hù)state和監(jiān)聽(tīng)變化  
 */
export default function createStore(reducer, preloadedState, enhancer) {
  // 如果第二個(gè)參數(shù)為func,redux認(rèn)為忽略了初始state,而是
  if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
    // enhancer增強(qiáng)劑,即利用中間件等來(lái)增強(qiáng)redux能力
    enhancer = preloadedState
    preloadedState = undefined
  }
  // 返回具有dispatch等屬性的對(duì)象 即store
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
 } 

按照一般的執(zhí)行順序,我們先看下對(duì)于參數(shù)的處理(平時(shí)大家也是一樣,一個(gè)函數(shù),執(zhí)行之前盡量判斷入?yún)⑹欠穹项A(yù)期,避免直接處理造成的錯(cuò)誤)

入?yún)⑻幚?/b>

對(duì)于三個(gè)參數(shù),后兩個(gè)是非必填的,但如果第二個(gè)參數(shù)是function,reduxe認(rèn)為其實(shí)encher,不然初始狀態(tài)是個(gè)函數(shù)不符合redux的預(yù)期,只能這樣處理了。

// 如果第二個(gè)參數(shù)為func,redux認(rèn)為忽略了初始state,而是
  if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
    // enhancer增強(qiáng)劑,即利用中間件等來(lái)增強(qiáng)redux能力
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== "undefined") {
    if (typeof enhancer !== "function") {
      throw new Error("Expected the enhancer to be a function.")
    }
    // 對(duì)于存在的enhancer,高階函數(shù)函數(shù)的用法,
    // 接受createStore返回一個(gè)增加功能之后的函數(shù),然后再傳入相關(guān)reducer得到store。
    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== "function") {
    throw new Error("Expected the reducer to be a function.")
  }
  // 一切符合預(yù)期,沒(méi)有 enhancer,那么初始賦值
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  // 監(jiān)聽(tīng)隊(duì)列
  let nextListeners = currentListeners
  // dispatch標(biāo)識(shí)
  let isDispatching = false
  
  // 初始狀態(tài)更新之后,聲明init狀態(tài)完成。
  dispatch({ type: ActionTypes.INIT })

dispatch的實(shí)現(xiàn)

dispatch的作用就是根據(jù)action,執(zhí)行對(duì)應(yīng)reducer以更新state。并執(zhí)行監(jiān)聽(tīng)隊(duì)列。
下面就來(lái)看dispatch的用法和實(shí)現(xiàn)。 常見(jiàn)使用:

// redux要求 參數(shù)必須為純對(duì)象
dispatch({ type: ActionTypes.INIT })

那么對(duì)于純對(duì)象,redux做了些什么呢

 /**
   * 通知方法,參數(shù)為純的js對(duì)象,標(biāo)明更新內(nèi)容
   * @param {Object} action 
   */
  function dispatch(action) {
    // 是否滿足純凈對(duì)象
    if (!isPlainObject(action)) {
      throw new Error(
        "省略"
      )
    }
    // 必須的type是否存在 
    if (typeof action.type === "undefined") {
      throw new Error(
        "省略"
      )
    }
    // 判斷是否處于某個(gè)action的dispatch中,大家一起dispatch可能死循環(huán)
    if (isDispatching) {
      throw new Error("Reducers may not dispatch actions.")
    }

    try {
      // 開(kāi)始dispatch,加鎖,標(biāo)明狀態(tài)
      isDispatching = true
      // 將當(dāng)前狀態(tài)和更新action,傳給當(dāng)前reducer處理
      // 這里可以回想下我們r(jià)educer中的兩個(gè)參數(shù),state和action 對(duì)應(yīng)的是什么
      /**
       * const todos = (state = [], action) => {
       */
      currentState = currentReducer(currentState, action)
    } finally {
      // 有異常,鎖置為false,不影響別的dispatch
      isDispatching = false
    }
    // 執(zhí)行dispatch,并且更新當(dāng)前監(jiān)聽(tīng)隊(duì)列為 最新隊(duì)列
    const listeners = (currentListeners = nextListeners)
    // 依次執(zhí)行,監(jiān)聽(tīng)器
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

createStore初始化完成之后會(huì)執(zhí)行dispatch({ type: ActionTypes.INIT }),此時(shí)執(zhí)行初始化操作。

我們要關(guān)注下currentState的計(jì)算,
將currentState,action傳給reducer處理,然后更新currentState。

針對(duì)初始化來(lái)說(shuō)currentState其實(shí)就是initState:

// 初始化狀態(tài)
let currentState = preloadedState
/****省略***/
// 這里可以回想下我們r(jià)educer中的兩個(gè)參數(shù),state和action對(duì)應(yīng)的值
currentState = currentReducer(currentState, action)

reducer示例:

const todos = (state = [], action) => {
switch (action.type) {
    case "ADD_TODO":
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    }
getSate實(shí)現(xiàn)

getState就是獲得store的state。這個(gè)比較簡(jiǎn)單。當(dāng)結(jié)合react-redux使用時(shí),其會(huì)幫我們進(jìn)行操作。我們就不用自行調(diào)用這個(gè)方法了,所以不要疑惑從哪里獲取的state。

/**
   * 返回應(yīng)用當(dāng)前狀態(tài)
   * 不過(guò)需要看下當(dāng)前是否有更新正在進(jìn)行,是的話則提示
   */
  function getState() {
    // 判斷是否isDispatching 中
    if (isDispatching) {
      throw new Error("省略")
    }
    return currentState
  }
subscribe

subscribe是比較重要的一個(gè)方法,用來(lái)供我們監(jiān)聽(tīng)狀態(tài)的變化,以執(zhí)行相關(guān)操作。
例如react-redux中的handleChange 會(huì)對(duì)是否pure組件及state進(jìn)行對(duì)比,以提升渲染效率。

示例:

 this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))

實(shí)現(xiàn): 返回的是一個(gè)函數(shù),可以進(jìn)行unsubscribe操作。

/** 
   * 訂閱通知
   */
  function subscribe(listener) {
    if (typeof listener !== "function") {
      throw new Error("Expected the listener to be a function.")
    }

    if (isDispatching) {
      throw new Error(
        "省略"
      )
    }
    // 是否已經(jīng)監(jiān)聽(tīng)過(guò)
    let isSubscribed = true
    // 監(jiān)聽(tīng)隊(duì)列是否相同,區(qū)分開(kāi),操作nextListeners
    ensureCanMutateNextListeners()
    // 新增監(jiān)聽(tīng)事件
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          "省略"
        )
      }
      // 注冊(cè)完成,可以進(jìn)行取消操作
      isSubscribed = false
      // 保持事件隊(duì)列 同步
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      // 刪除監(jiān)聽(tīng)事件
      nextListeners.splice(index, 1)
    }
  }

replaceReducer

這個(gè)開(kāi)發(fā)比較少用,用于熱更新

// 用于reducer的熱替換,開(kāi)發(fā)中一般不會(huì)直接使用
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Expected the nextReducer to be a function.")
    }

    currentReducer = nextReducer
    // 更新值之后,進(jìn)行dispatch。
    dispatch({ type: ActionTypes.REPLACE })
  }

到這里createStore已經(jīng)解析完成了,大家應(yīng)該了解該方法到底做了些什么操作吧。
簡(jiǎn)單概括一下就是:接收reducer和initState,返回一個(gè)store 對(duì)象。該對(duì)象提供了監(jiān)聽(tīng)、分發(fā)等功能,以實(shí)現(xiàn)數(shù)據(jù)的更新。

實(shí)際使用中的問(wèn)題

經(jīng)過(guò)上面的解讀之后,對(duì)于redux的常規(guī)應(yīng)用應(yīng)該有所了解了。不過(guò)實(shí)際使用中可能會(huì)遇到些問(wèn)題。
例如action要求是純對(duì)象,而我們獲取數(shù)據(jù)一般是異步的,這就需要借助redux-thunk這個(gè)中間件了。
actionCreater返回一個(gè)函數(shù)。如下:

export function func1() {
  return dispatch => {
      dispatch({
      type:"test",
      data:"a"
      })
  }
}

在了解如何實(shí)現(xiàn)之前,需要先看下redux中間件的原理。 因?yàn)閞educer更多的關(guān)注的是數(shù)據(jù)的操作,對(duì)于一些公共的方法,需要抽離出來(lái),不過(guò)這些方法在何時(shí)使用呢,redux為我們提供了中間件來(lái)滿足需求。

redux中間件原理

redux 借鑒了 Koa里 middleware 的思想,即鼎鼎大名的洋蔥模型。

不過(guò)這里請(qǐng)求對(duì)應(yīng)的是dispatch的過(guò)程。

每次dispatch的過(guò)程中,都要依次將中間件執(zhí)行一遍。
遇到阻塞或者操作完成,執(zhí)行下個(gè)中間件,直到執(zhí)行完成,以便我們事先日志,監(jiān)控、異常上報(bào)等功能。
那么redux 又是如何支持中間件的呢。這就離不開(kāi)applyMiddleware了。
這里前面的

applyMiddleware實(shí)現(xiàn)思路

實(shí)現(xiàn)思想比較簡(jiǎn)單,通過(guò)科里化和compose,為符合規(guī)范的中間件分配訪問(wèn)dispatch和store的途徑,以便在不同階段來(lái)自定義數(shù)據(jù)更新。
例如異步操作,返回的不是對(duì)象,那么就執(zhí)行返回的函數(shù),然后調(diào)用下一個(gè)中間件。等異步請(qǐng)求結(jié)束,再次dispatch 對(duì)應(yīng)的action。

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.`
      )
    }
    // 賦予每個(gè)中間件訪問(wèn)store的能力。
    const middlewareAPI = {
      getState: store.getState,
      // 箭頭函數(shù)保存dispatch,保證其的同步更新
      dispatch: (...args) => dispatch(...args)
    }
    // 串聯(lián)中間件,并賦予每個(gè)中間件訪問(wèn)dispatch的能力。
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 關(guān)聯(lián)dispatch與中間件,組合調(diào)用之后得到類似下面的新對(duì)象
    // dispatch = f1(f2(f3(store.dispatch))));
    dispatch = compose(...chain)(store.dispatch)
    

    return {
      ...store,
      dispatch
    }
  }
}

這樣執(zhí)行之后返回的,對(duì)象就是增強(qiáng)之后的store了。

compose的實(shí)現(xiàn)

redux中compose是柯里化函數(shù)的一個(gè)示例,目的是將函數(shù)串聯(lián)起來(lái)。

/**
 * 函數(shù)組合,科里化的串聯(lián)
 */
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)))
}
結(jié)合redux-thunk示例

redux-thunk源碼,實(shí)現(xiàn)也很優(yōu)雅,對(duì)于返回的function,將dispatch等參數(shù)傳遞進(jìn)去,然后執(zhí)行,等待回調(diào)異步完成再dispatch。對(duì)于正常對(duì)象則進(jìn)行下一步。

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    // 每次dispatch的時(shí)候都會(huì)進(jìn)行判斷,如果是個(gè)函數(shù),那就執(zhí)行函數(shù),不再進(jìn)行下一步吧,這樣就避免了,函數(shù)不滿足action要求的問(wèn)題
    if (typeof action === "function") {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

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

export default thunk;

那么實(shí)際使用時(shí),在createStore時(shí)加入該中間件即可:

import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk";
const store = createStore(
  reducer,
  applyMiddleware({
  ...middleware,
  thunk})
)

那么到這里對(duì)于redux的中間件 也就是問(wèn)題2,我想大家也比較清楚了。
對(duì)于常見(jiàn)中間件可以參考

結(jié)束語(yǔ) 參考文章

redux中文文檔
深入React技術(shù)棧
加上重讀redux源碼一和帶著問(wèn)題看 react-redux 源碼實(shí)現(xiàn)總算將redux及react-redux重讀了一遍??赡苡腥藭?huì)說(shuō)道這些源碼,看完也會(huì)忘,有這個(gè)必要嗎。我感覺(jué)分情況來(lái)看,如果我們只是使用,那么看官方文檔就可以了,當(dāng)遇到某些疑問(wèn)好像找不到貼切解釋的時(shí)候,不放一看。
此外也是學(xué)習(xí)大佬們的設(shè)計(jì)思路和實(shí)現(xiàn)方式,有的放矢才能開(kāi)卷有益。

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

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

相關(guān)文章

  • React生態(tài),dva源碼閱讀

    摘要:下面會(huì)從淺到深,淡淡在閱讀源碼過(guò)程中自己的理解。分拆子頁(yè)面后,每一個(gè)子頁(yè)面對(duì)應(yīng)一個(gè)文件??偨Y(jié)上面就是最早版本的源碼,很簡(jiǎn)潔的使用了等其目的也很簡(jiǎn)單簡(jiǎn)化相關(guān)生態(tài)的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯(cuò)的,大大提升了開(kāi)發(fā)效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態(tài)管理,以及Redux-saga中...

    bergwhite 評(píng)論0 收藏0
  • React生態(tài),dva源碼閱讀

    摘要:下面會(huì)從淺到深,淡淡在閱讀源碼過(guò)程中自己的理解。分拆子頁(yè)面后,每一個(gè)子頁(yè)面對(duì)應(yīng)一個(gè)文件??偨Y(jié)上面就是最早版本的源碼,很簡(jiǎn)潔的使用了等其目的也很簡(jiǎn)單簡(jiǎn)化相關(guān)生態(tài)的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯(cuò)的,大大提升了開(kāi)發(fā)效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態(tài)管理,以及Redux-saga中...

    txgcwm 評(píng)論0 收藏0
  • React生態(tài),dva源碼閱讀

    摘要:下面會(huì)從淺到深,淡淡在閱讀源碼過(guò)程中自己的理解。分拆子頁(yè)面后,每一個(gè)子頁(yè)面對(duì)應(yīng)一個(gè)文件。總結(jié)上面就是最早版本的源碼,很簡(jiǎn)潔的使用了等其目的也很簡(jiǎn)單簡(jiǎn)化相關(guān)生態(tài)的繁瑣邏輯參考源碼地址 ??dva的思想還是很不錯(cuò)的,大大提升了開(kāi)發(fā)效率,dva集成了Redux以及Redux的中間件Redux-saga,以及React-router等等。得益于Redux的狀態(tài)管理,以及Redux-saga中...

    harryhappy 評(píng)論0 收藏0
  • 重讀《學(xué)習(xí)JavaScript數(shù)據(jù)結(jié)構(gòu)與算法-第三版》- 第3章 數(shù)組(

    摘要:定場(chǎng)詩(shī)守法朝朝憂悶,強(qiáng)梁夜夜歡歌損人利己騎馬騾,正值公平挨餓修橋補(bǔ)路瞎眼,殺人放火兒多我到西天問(wèn)我佛,佛說(shuō)我也沒(méi)轍前言讀學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法第章數(shù)組,本小節(jié)將繼續(xù)為各位小伙伴分享數(shù)組的相關(guān)知識(shí)數(shù)組的新功能。 定場(chǎng)詩(shī) 守法朝朝憂悶,強(qiáng)梁夜夜歡歌; 損人利己騎馬騾,正值公平挨餓; 修橋補(bǔ)路瞎眼,殺人放火兒多; 我到西天問(wèn)我佛,佛說(shuō):我也沒(méi)轍! 前言 讀《學(xué)習(xí)JavaScript數(shù)據(jù)結(jié)構(gòu)與算法...

    William_Sang 評(píng)論0 收藏0
  • 重讀 Gulp

    摘要:當(dāng)接收一個(gè)回調(diào)函數(shù)的時(shí)候,一定要注意回調(diào)函數(shù)中的參數(shù)。主要作用就是用來(lái)讀取文件或者文件夾中的數(shù)據(jù)。表示文件的名稱指的是發(fā)生的變化使用技巧的進(jìn)一步使用,可以參照中文官網(wǎng)中的技巧集。 Gulp 簡(jiǎn)介 Gulp 對(duì)現(xiàn)在的前端而言,是一個(gè)稍微老舊的工具了,但是,為了復(fù)習(xí)以前學(xué)過(guò)的內(nèi)容,還是把它翻出來(lái),放在自己的博客中。說(shuō)不定哪天又用到了呢。 需要說(shuō)明的是,這里使用的 Gulp 版本是 3.9....

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

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

0條評(píng)論

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