博客 / 詳情

返回

從源碼分析Redux 和 Mobx 那個更優美 ,一起探索誰絲滑!

前言

探索Redux 和 Mobx 原理從我做起,從這篇文章看起!

所以

一位程序員的職業生涯大約十年,只有人壽命的十分之一。前端項目只是你生活工作的一部分,而你卻是它的全部,你是他的靈魂。請放下長時間的遊戲、工作時的摸魚。多學習來以最完美的狀態好好陪你項目!

正文

這篇文章將會詳細分析 Redux 和 Mobx 核心 Api, 看一遍學不會就看兩次、三次、手寫一次!

知識點

  • Redux 基本使用(沒有)
  • createStore
  • bindActionCreators
  • combineReducers
  • applyMiddleware
  • 彩蛋 thunk

Redux.png

  • Mobx 基本使用(沒有)
  • observable
  • autorun
  • observer

這個 代碼很多哦 , 我這菜鳥看不懂 ,我看了文章,應該能實現 這些api 和 vue的的 響應式差不多.

Redux

createStore

這個是個比較核心的函數之創建倉庫,分為了3個核心函數 dispatch,getState,subscribe

這三個函數的基本使用是這樣的...

// 構建倉庫
const store = createStore(reducer);

const unListen = store.subscribe(() => {
    console.log("監聽器1", store.getState());
})

store.dispatch(createAddUserAction({
    id: 1,
    name: "遇見同學",
    age: 21
}));

unListen(); //取消監聽

本質就如思維導圖思路

createStore

基本實現且看代碼的詳細解釋


/**
 * 得到一個指定長度的隨機字符串
 * @param {*} length 
 */
function getRandomString(length) {
    return Math.random().toString(36).substr(2, length).split("").join(".")
}

/**
 * 判斷某個對象是否是一個plain-object
 * @param {*} obj 
 */
function isPlainObject(obj) {
    if (typeof obj !== "object") {
        return false;
    }
    return Object.getPrototypeOf(obj) === Object.prototype;
}

// 上面連個都為輔助工具函數

/**
 * 實現createStore的功能
 * @param {function} reducer reducer
 * @param {any} defaultState 默認的狀態值
 */
export default function createStore(reducer, defaultState) {
    let currentReducer = reducer, //當前使用的reducer
        currentState = defaultState; //當前倉庫中的狀態

    const listeners = [];  //記錄所有的監聽器(訂閲者)

    function dispatch(action) {
        //驗證action
        if (!isPlainObject(action)) {
            throw new TypeError("action must be a plain object");
        }
        //驗證action的type屬性是否存在
        if (action.type === undefined) {
            throw new TypeError("action must has a property of type");
        }
        currentState = currentReducer(currentState, action)
        //運行所有的訂閲者(監聽器)
        for (const listener of listeners) {
            listener();
        }
    }

    function getState() {
        return currentState;
    }

    /**
     * 添加一個監聽器(訂閲器)
     */
    function subscribe(listener) {
            if (typeof listener !== 'function') {
              throw new Error(
                `Expected the listener to be a function. Instead, received: '${kindOf(
                  listener
                )}'`
              )
            }
        listeners.push(listener); //將監聽器加入到數組中
        let isSubscribed = false;//是否已經移除掉了
        return function () {
            if (isSubscribed) {
                return;
            }
            //將listener從數組中移除
            const index = listeners.indexOf(listener);
            listeners.splice(index, 1);
            isSubscribed = true;
        }
    }

    //創建倉庫時,需要分發一次初始的action
    dispatch({
        type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
    })

    return {
        dispatch,
        getState,
        subscribe
    }
}

實現的代碼基本對標 Github Redux

由於官方使用的是Ts 寫的 閲讀起來可能更加困難

所以過濾許多的細節處理和TS,將本質的思維呈現出來!

從入口帶你理解下一個核心函數bindActionCreators

bindActionCreators
  • 作用
    用於返回可直接調用的 dispatch 的函數方法
  • 參數

    • object | function
    • (store.dispatch)
    const actionCreators = {
      addUser: createAddUserAction,
      deleteUser: createDeleteUserAction
    }
    
    // 這可直接傳入一個函數
    const actions = bindActionCreators(actionCreators, store.dispatch)
    
    actions.addUser({ id: 1, name: "遇見同學", age: 21 })

使用還是非常簡單的, 本質也是一個函數, 非常的精妙,一起看看吧!

bindActionCreators

思路是這樣,核心函數是另外一個getAutoDispatchActionCreator

/**
 * @param {object | function } actionCreators 
 * @param {*} dispatch 
 * @returns 
 */
export default function (actionCreators,dispatch) {
    // 為函數時 直接調用自動分發的action創建函數
    if(typeof actionCreators === 'function'){
        _getAutoDispatchActionCreator(actionCreators,dispatch)
     // 為對象時 遍歷對象 轉化為已創建自動分發的action創建函數的對象
    }else if(typeof actionCreators === 'object'){
        const result = {}
        Object.keys(actionCreators).forEach((key)=>{
            result[key] = _getAutoDispatchActionCreator(actionCreators[key],dispatch)
        })
        return result
    }else{
        throw new TypeError("actionCreators must be an object or function which means action creator")
    }

}
/**
 * 自動分發的action創建函數
 * @param {function} actionCreator 
 * @param {any} dispatch 
 * @returns 
 */
function _getAutoDispatchActionCreator(actionCreator, dispatch){
    return function(...args){
       return dispatch(actionCreator(...args))
    }
}

是不是豁然開朗了,乘熱打鐵進入下一個函數吧combineReducers

combinReducers

基本思路是這樣的

combinReducers

**
 * 合併reducers函數
 * @param {object} reducers 
 * @returns 
 */
export default function (reducers) {
    validateReducers(reducers);
    /**
     * 返回的是一個reducer函數
     */
    return function (state = {}, action) {
        const newState = {}; //要返回的新的狀態
        for (const key in reducers) {
            if (reducers.hasOwnProperty(key)) {
                const reducer = reducers[key];
                newState[key] = reducer(state[key], action);
            }
        }
        return newState; //返回狀態
    }
}

function validateReducers(reducers) {
    if (typeof reducers !== "object") {
        throw new TypeError("reducers must be an object");
    }
    if (!isPlainObject(reducers)) {
        throw new TypeError("reducers must be a plain object");
    }
    //驗證reducer的返回結果是不是undefined
    for (const key in reducers) {
        if (reducers.hasOwnProperty(key)) {
            const reducer = reducers[key];//拿到reducer
            //傳遞一個特殊的type值
            let state = reducer(undefined, {
                type:`@@redux/INIT${getRandomString(6)}`
            })
            if (state === undefined) {
                throw new TypeError("reducers must not return undefined");
            }
            state = reducer(undefined, {
                type: `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
            })
            if (state === undefined) {
                throw new TypeError("reducers must not return undefined");
            }
        }
    }
}

來對比一下這個Redux 的 combinReducers官方代碼

總得來説這幾個都屬於輔助函數,而createStore傳入的reducer 才是調動整個 狀態數據的核心, 不過Redux 提供了 一個核心機制 中間件 也是Redux 的靈魂之一。

接下來看下 applyMiddleware 是如何實現的

applyMiddleware

KoaRedux 等優秀庫都提供了中間思想, 中間件一出, 必有 組合函數

我們看下 Redux 的組合函數

function compose(...funcs: Function[]) {
  return funcs.reduce(
    (a, b) =>
      (...args: any) =>
        a(b(...args))
  )
}

省略一些邊界判斷 , 實現就一個 reducer

就這一行

funcs.reduce((a, b) => (...args) => a(b(...args)))

函數由內向外,返回作為 reducer 的新的累加值 依次執行 b、a、...

再看看applyMiddleware函數

import compose from "./compose"
/**
 * 註冊中間件
 * @param  {...any} middlewares 所有的中間件 本質是一個函數
 */
export default function (...middlewares) {
    return function (createStore) { //給我創建倉庫的函數
        //下面的函數用於創建倉庫
        return function (reducer, defaultState) {
            //創建倉庫
            const store = createStore(reducer, defaultState);
            let dispatch = () => { throw new Error("目前還不能使用dispatch") };
            const simpleStore = {
                getState: store.getState,
                dispatch: (...args) => dispatch(...args)
            }
            //給dispatch賦值
            //根據中間件數組,得到一個dispatch創建函數的數組
            const dispatchProducers = middlewares.map(mid => mid(simpleStore));
            dispatch = compose(...dispatchProducers)(store.dispatch);
            return {
                ...store,
                dispatch
            }
        }
    }
}

以我理解幹了這幾件事

applyMiddleware

調用返回了 一個具備以上能力的函數 拋出給 createStore 的第二個參數, 我們看看 createStore 有何改動

export default function createStore(reducer, defaultState, enhanced) {
    //enhanced表示applymiddleware返回的函數
    if (typeof defaultState === "function") {
        //第二個參數是應用中間件的函數返回值
        enhanced = defaultState;
        defaultState = undefined;
    }
    if (typeof enhanced === "function") {
        //進入applyMiddleWare的處理邏輯
        return enhanced(createStore)(reducer, defaultState);
    }

   // ....同上面代碼

    return {
        dispatch,
        getState,
        subscribe
    }
}
  • 執行applyMiddleware 返回一個需要倉庫函數 的函數

     applyMiddleware(
          thunk, // 返回中間件函數
          logger
    )
  • 接着 傳入createStore 倉庫函數函數本身 很巧妙
  • 得到一個新的倉庫構造函數 傳入 (reducer, defaultState)
  • 用第一次傳入的 createStore(reducer, defaultState) 執行構造出沒使用中間件的倉庫
  • 掛載 storedispatch 新對象傳給 每一箇中間件函數,返回新的dispatch數組
  • 合併所有新的dispatch,執行第一次
  • 返回 store 和 新的 dispatch

這便是 中間件的基本 運行流程 有些繞 好好學習,天天向上

送你一個彩蛋 thunk 實現

代碼是個只有幾行的函數


function createThunkMiddleware(extraArgument) {
    return ({ dispatch, getState }) => (next) => (action) => {
      if (typeof action === 'function') {
        return action(dispatch, getState, extraArgument);
      }
      return next(action);
    };
  }
  // 創建 thunk 中間件
  const thunk = createThunkMiddleware();
  thunk.withExtraArgument = createThunkMiddleware;
  
  export default thunk;

看圖 我帶你 穿起來 代碼 一起解讀

thunk

紅色部分是 中間件函數dispatch, getState 丟給 thunk , 接着返回函數

usethunk

圖中紅絲部分代表着 thunk 處理部分 , 判斷是否 為 函數 不是就對給下步去執行

我們再看下 next 是什麼。

thunk

清晰的看一下 next 就是 dispatch 函數

後面兩個 action 就是 一個普通 平面對象 類型異步函數 thunk 處理類型

Mobx

Mobx 也是一款狀態管理工具 ,但是思維模式不同

Mobx

類似 Vue 響應式原理 , 配合 觀察者模式 收集依賴 實現

Api 有點多 我沒看懂 , 下面僅供參考

observable

這個方法顯而易見 構建出 一個 可觀察的對象

import reaction from './reaction'
export default function observable(target,key,descritor){
    // 當使用裝飾器 修飾屬性時
    if( typeof key === 'string'){
     let Reaction = new reaction
     let v =  descritor.initailizer()
     v = createObservable(v)
     return {
       enumerable:true,
       configurable:true,
       get(){
        Reaction.collect()
        return v
       },
       set(value){
        v = value
        Reaction.run()
       },
     }
    }
      return createObservable(target)
  } 
  
  function createObservable(val){
      let handle = () =>{
      let Reaction = new reaction
       return {
         get(target,key){
          Reaction.collect()
          return Reflect.get(target,key)
         },
         set(target,key,value){
          let v = Reflect.set(target,key,value)
          Reaction.run()
          return v
         },
       }
      }   
      return deepProxy(val,handle)
  }
  
  function deepProxy(val,handle){
    if(typeof val !== 'object') return val
  
    // 從後往前依此實現代理
    for(let key in val){
      val[key] = deepProxy(val[key],handle)
    }
  
    return createObservable(val, handle())
  }

可以很清楚的看到 這個函數實現了以下

  • 判斷是否處於裝飾器修飾使用狀態
  • Proxy觀察一個深度對象
  • 在get時觸發 依賴收集
reaction
let nowFn = null
let counter = 0
class Reaction {
  constructor(){
    this.id = ++counter
    this.store = {}

  }
  run(){
    if(this.store[this.id]){
      this.store[this.id].forEach(w=>w())
    }
  }
  collect(){
    if(nowFn){
      this.store[this.id] = this.store[this.id] || []
      this.store[this.id].push(nowFn)
    }
  }
  static start(handle){
    nowFn = handle
  }
  static end (){
    nowFn = null
  }
}    
autorun
  import reaction from './reaction'
  export default function autorun(handle){
    reaction.start()
    handle()
    reaction.end()
  }

再 看看 autorunreaction

基本就是一個簡單的依賴收集 和 觸發

相信 各位 知道 觀察者模式 和 瞭解 Vue 依賴收集的對這樣的寫法並不陌生

而 最後的一個 observer

observer
/**
 * 裝飾器 mobx-react 修飾類組件
 * @param {*} target 
 */
export default function observer(target){
    let cwm = target.prototype.componentWillMount;
    target.prototype.componentWillMount = function(){
      cwm && cwm.call(this);
      autorun(()=>{
        this.render();
        this.forceUpdate();
      })
    }
  }

這個僅只是 修飾 react 類組件的 狀態

對比

Redux
  • 單一數據源
  • 狀態數據只讀
  • 使用純函數修改狀態
Mobx
  • 多數據源
  • 被觀察對象
  • 觀察依賴
  • 觸發動作
兩者的區別:
比較 redux mobx
核心模塊 Action,Reducer,Store,沒有調度器的概念 observerState、Derivations、Actions...
Store 只有一個 Store, Store 和更改邏輯是分開的,帶有分層 reducer的單一 Stor 多個 store
狀態 狀態是不可改變的(建議不可變值) 通常將狀態包裝成可觀察對象,觀察者依賴收集模式
編程思想 遵循函數式編程思想 函數響應式編程編程
對象 JavaScript對象 可觀察對象

總結

  • Redux 的 源碼實現 瞭解中間件的核心思想
  • 實現Mobx 的 核心 (其他的慢慢摸索是吧)
  • 對比兩中狀態管理的 差異 優劣
本文首發 GitHub |
源碼 Analysis
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.