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

資訊專欄INFORMATION COLUMN

解密Redux: 從源碼開始

remcarpediem / 2585人閱讀

摘要:接下來筆者就從源碼中探尋是如何實現(xiàn)的。其實很簡單,可以簡單理解為一個約束了特定規(guī)則并且包括了一些特殊概念的的發(fā)布訂閱器。新舊中存在的任何都將收到先前的狀態(tài)。這有效地使用來自舊狀態(tài)樹的任何相關(guān)數(shù)據(jù)填充新狀態(tài)樹。

Redux是當今比較流行的狀態(tài)管理庫,它不依賴于任何的框架,并且配合著react-redux的使用,Redux在很多公司的React項目中起到了舉足輕重的作用。接下來筆者就從源碼中探尋Redux是如何實現(xiàn)的。

注意:本文不去過多的講解Redux的使用方法,更多的使用方法和最佳實踐請移步Redux官網(wǎng)。
源碼之前 基礎概念

隨著我們項目的復雜,項目中的狀態(tài)就變得難以維護起來,這些狀態(tài)在什么時候,處于什么原因,怎樣變化的我們就很難去控制。因此我們考慮在項目中引入諸如Redux、Mobx這樣的狀態(tài)管理工具。

Redux其實很簡單,可以簡單理解為一個約束了特定規(guī)則并且包括了一些特殊概念的的發(fā)布訂閱器。

在Redux中,我們用一個store來管理一個一個的state。當我們想要去修改一個state的時候,我們需要去發(fā)起一個action,這個action告訴Redux發(fā)生了哪個動作,但是action不能夠去直接修改store里頭的state,他需要借助reducer來描述這個行為,reducer接受state和action,來返回新的state。

三大原則

在Redux中有三大原則:

單一數(shù)據(jù)源:所有的state都存儲在一個對象中,并且這個對象只存在于唯一的store中;

state只讀性:唯一改變state的方法就是去觸發(fā)一個action,action用來描述發(fā)生了哪個行為;

使用純函數(shù)來執(zhí)行修改:reducer描述了action如何去修改state,reducer必須是一個純函數(shù),同樣的輸入必須有同樣的輸出;

剖析源碼 項目結(jié)構(gòu)

拋去一些項目的配置文件和其他,Redux的源碼其實很少很簡單:

index.js:入口文件,導出另外幾個核心函數(shù);

createStore.js:store相關(guān)的核心代碼邏輯,本質(zhì)是一個發(fā)布訂閱器;

combineReducers.js:用來合并多個reducer到一個root reducer的相關(guān)邏輯;

bindActionCreators.js:用來自動dispatch的一個方法;

applyMiddleware.js:用來處理使用的中間件;

compose.js:導出一個通過從右到左組合參數(shù)函數(shù)獲得的函數(shù);

utils:兩個個工具函數(shù)和一個系統(tǒng)注冊的actionType;

從createStore來講一個store的創(chuàng)建

首先我們先通過createStore函數(shù)的入?yún)⒑头祷刂祦砗喴斫馑墓δ埽?/p>

export default function createStore(reducer, preloadedState, enhancer) {

  // ...

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

createStore接受三個參數(shù):

reducer:用來描述action如何改變state的方法,它給定當前state和要處理的action,返回下一個state;

preloadedState:顧名思義就是初始化的state;

enhancer:可以直譯為增強器,用它來增強store的第三方功能,Redux附帶的唯一store增強器是applyMiddleware;

createStore返回一個對象,對象中包含使用store的基本函數(shù):

dispatch:用于action的分發(fā);

subscribe:訂閱器,他將會在每次action被dispatch的時候調(diào)用;

getState:獲取store中的state值;

replaceReducer:替換reducer的相關(guān)邏輯;

接下來我們來看看createStore的核心邏輯,這里我省略了一些簡單的警告和判斷邏輯:

export default function createStore(reducer, preloadedState, enhancer) {
  // 判斷是不是傳入了過多的enhancer
  // ...

  // 如果不傳入preloadedState只傳入enhancer可以寫成,const store = createStore(reducers, enhancer)
  // ...

  // 通過在增強器傳入createStore來增強store的基本功能,其他傳入的參數(shù)作為返回的高階函數(shù)參數(shù)傳入;
  if (typeof enhancer !== "undefined") {
    if (typeof enhancer !== "function") {
      throw new Error("Expected the enhancer to be a function.")
    }
    return enhancer(createStore)(reducer, preloadedState)
  }

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

  // 閉包內(nèi)的變量;
  // state作為內(nèi)部變量不對外暴露,保持“只讀”性,僅通過reducer去修改
  let currentReducer = reducer
  let currentState = preloadedState
  // 確保我們所操作的listener列表不是原始的listener列表,僅是他的一個副本;
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 確保我們所操作的listener列表不是原始的listener列表,僅是他的一個副本;
  // 只有在dispatch的時候,才會去將currentListeners和nextListeners更新成一個;
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 通過閉包返回了state,state僅可以通過此方法訪問;
  function getState() {
    // 判斷當前是否在dispatch過程中
    // ...

    return currentState
  }

  // Redux內(nèi)部的發(fā)布訂閱器
  function subscribe(listener) {
    // 判斷l(xiāng)istener的合法性
    // ...

    // 判斷當前是否在dispatch過程中
    // ...

    let isSubscribed = true

    // 復制一份當前的listener副本
    // 操作的都是副本而不是源數(shù)據(jù)
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

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

      // 判斷當前是否在dispatch過程中
      // ...

      isSubscribed = false

      ensureCanMutateNextListeners()

      // 根據(jù)當前l(fā)istener的索引從listener數(shù)組中刪除來實現(xiàn)取掉訂閱;
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  function dispatch(action) {
    // 判斷action是不是一個普通對象;
    // ...

    // 判斷action的type是否合法
    // ...

    // 判斷當前是否在dispatch過程中
    // ...

    try {
      isDispatching = true
      // 根據(jù)要觸發(fā)的action, 通過reducer來更新當前的state;
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 通知listener執(zhí)行對應的操作;
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 替換reducer,修改state變化的邏輯
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Expected the nextReducer to be a function.")
    }

    currentReducer = nextReducer

    // 此操作對ActionTypes.INIT具有類似的效果。
    // 新舊rootReducer中存在的任何reducer都將收到先前的狀態(tài)。
    // 這有效地使用來自舊狀態(tài)樹的任何相關(guān)數(shù)據(jù)填充新狀態(tài)樹。
    dispatch({ type: ActionTypes.REPLACE })
  }

  function observable() {
    const outerSubscribe = subscribe
    return {
      // 任何對象都可以被用作observer,observer對象應該有一個next方法
      subscribe(observer) {
        if (typeof observer !== "object" || observer === null) {
          throw new TypeError("Expected the observer to be an object.")
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        const unsubscribe = outerSubscribe(observeState)
        // 返回一個帶有unsubscribe方法的對象可以被用來在store中取消訂閱
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

  // 創(chuàng)建store時,將調(diào)度“INIT”操作,以便每個reducer返回其初始狀態(tài),以便state的初始化。
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
從combineReducers談store的唯一性

僅靠上面的createStore其實已經(jīng)可以完成一個簡單的狀態(tài)管理了,但是隨著業(yè)務體量的增大,state、action、reducer也會隨之增大,我們不可能把所有的東西都塞到一個reducer里,最好是劃分成不同的reducer來處理不同模塊的業(yè)務。

但是也不能創(chuàng)建多個store維護各自的reducer,這就違背了Redux的單一store原則。為此,Redux提供了combineReducers讓我們將按照業(yè)務模塊劃分的reducer合成一個rootReducer。

接下來我們看看combineReducers的源碼,這里也是去掉了一些錯誤警告的代碼和一些錯誤處理方法:

export default function combineReducers(reducers) {
  // 取出所有的reducer遍歷合并到一個對象中
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    // 判斷未匹配的refucer
    // ...

    if (typeof reducers[key] === "function") {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

   // 錯誤處理的一些邏輯
   // ...

  return function combination(state = {}, action) {

    // 錯誤處理的一些邏輯
    // ...

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      // 對應的reducer
      const reducer = finalReducers[key]
      // 根據(jù)指定的reducer找到對應的state
      const previousStateForKey = state[key]
      // 執(zhí)行reducer, 返回當前state
      const nextStateForKey = reducer(previousStateForKey, action)
      // nextStateForKey undefined的一些判斷
      // ...

      // 整合每一個reducer對應的state
      nextState[key] = nextStateForKey
      // 判斷新的state是不是同一引用, 以檢驗reducer是不是純函數(shù)
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

其實到這里可以簡單的看出combineReducers就是把多個reducer拉伸展開到到一個對象里,同樣也把每一個reducer里的state拉伸到一個對象里。

從bindActionCreators談如何自動dispatch

現(xiàn)有的store每一次state的更新都需要手動的dispatch每一個action,而我們其實更需要的是自動的dispatch所有的action。這里就用到了bindActionCreators方法。

現(xiàn)在我們來看看bindActionCreators的源碼

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 返回綁定了this的actionCreator
  if (typeof actionCreators === "function") {
    return bindActionCreator(actionCreators, dispatch)
  }

  // actionCreators類型判斷的錯誤處理
  // ...

  // 為每一個actionCreator綁定this
  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === "function") {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

其實我們在react項目中對這個方法是幾乎無感知的,因為是在react-redux的connect中調(diào)用了這個方法來實現(xiàn)自動dispatch action的,不然需要手動去dispatch一個個action。

從compose談函數(shù)組合

compose是Redux導出的一個方法,這方法就是利用了函數(shù)式的思想對函數(shù)進行組合:

// 通過從右到左組合參數(shù)函數(shù)獲得的函數(shù)。例如,compose(f, g, h)與do(...args)=> f(g(h(... args)))相同。
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)))
}
從applyMiddleware談如何自定義dispatch

我們的action會出現(xiàn)同步的場景,當然也會出現(xiàn)異步的場景,在這兩種場景下dispacth的執(zhí)行時機是不同的,在Redux中,可以使用middleware來對dispatch進行改造,下面我們來看看applyMiddleware的實現(xiàn):

import compose from "./compose"

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))
    // 通過從右到左組合參數(shù)函數(shù)獲得的函數(shù)。例如,compose(f, g, h)與do(...args)=> f(g(h(... args)))相同。
    // 對dispatch改造
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
結(jié)語

到此,Redux源碼的部分就分析完了,但是在具體和React結(jié)合的時候還需要用到react-redux,下一篇文章,我將深入到react-redux的源碼學習,來探索在react中,我們?nèi)绾稳ナ褂肦edux。

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

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

相關(guān)文章

  • 前端最實用書簽(持續(xù)更新)

    摘要:前言一直混跡社區(qū)突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來有點混亂所以將前端主流技術(shù)做了一個書簽整理不求最多最全但求最實用。 前言 一直混跡社區(qū),突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來有點混亂; 所以將前端主流技術(shù)做了一個書簽整理,不求最多最全,但求最實用。 書簽源碼 書簽導入瀏覽器效果截圖showImg(https://segmentfault.com/img/bVbg41b?w=107...

    sshe 評論0 收藏0
  • React 之容器組件和展示組件相分離解密

    摘要:的綁定庫包含了容器組件和展示組件相分離的開發(fā)思想。明智的做法是只在最頂層組件如路由操作里使用。其余內(nèi)部組件僅僅是展示性的,所有數(shù)據(jù)都通過傳入。 Redux 的 React 綁定庫包含了 容器組件和展示組件相分離 的開發(fā)思想。明智的做法是只在最頂層組件(如路由操作)里使用 Redux。其余內(nèi)部組件僅僅是展示性的,所有數(shù)據(jù)都通過 props 傳入。 那么為什么需要容器組件和展示組件相分離呢...

    QLQ 評論0 收藏0
  • 結(jié)合 Google quicklink,react 項目實現(xiàn)頁面秒開

    摘要:最后,狀態(tài)管理與同構(gòu)實戰(zhàn)這本書由我和前端知名技術(shù)大佬顏海鏡合力打磨,凝結(jié)了我們在學習實踐框架過程中的積累和心得。 對于前端資訊比較敏感的同學,可能這兩天已經(jīng)聽說了 GoogleChromeLabs/quicklink這個項目:它由 Google 公司著名開發(fā)者 Addy Osmani 發(fā)起,實現(xiàn)了:在空閑時間預獲取頁面可視區(qū)域內(nèi)的鏈接,加快后續(xù)加載速度。如果你沒有聽說過 Addy Os...

    warkiz 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<