redux中的compose與applyMiddleware

彭博發表於2021-10-14

compose 方法

compose 函式定義

compose方法利用陣列的 reduce 方法,將多個方法按照一定的順序組合為一個方法,從而達到一種函式自動有序執行的效果。其中各個方法的執行的順序取決於 reduce 方法如何應用。compose的簡單實現如下:

// compose 接受多個函式作為入參 fun1 fun2
function compose(...funs){
  const len = funs.length 
  // 處理兩種特殊情況,函式數量 為 0 || 1
  if(!len) return (...arg) => arg
  if(len === 1) return funs[0]
  // 以下組合方式 b 方法 會 先於 a 方法執行
  return funs.reduce((a,b) => (...arg) => a(b(arg)))
  // 以下組合方式 a 方法會先於 b 方法執行
  // return funs.reduce((a,b) => (...arg) => b(a(arg)))
}

被組合函式(a,b)的形式

  1. b 函式的返回值為普通的資料,作為a 函式的入參

    function b(){
      // 如返回值為普通的物件
      return {...}
    }
    function a(arg){
      // arg 資料應用 || 處理
      return {...}
    }

    按照以上形式的這種形式,a 函式若想應用b函式的返回值,最好預先知道b函式返回值的資料型別及欄位細節,這會導致 a,b 方法之間的耦合嚴重

  2. b 返回一個函式,作為 a 函式的入參

    function b(){
      // 返回值為函式
      return (...arg) => { 
     //...
      }
    }
    function a(next){
      // next 前置操作
      // next() 可以在這裡操作 next
      // next 後置操作
      return (...arg) => {
     // next 前置操作
     // next() 可以在這裡操作 next
     // next 後置操作
      }
    }

    相比上一種形式,這種形式可以使得函式之間達到解偶效果,即函式 a 無需關心函式 b 的返回值,只要知道其返回值是一個函式,並在自己認為合適的時機呼叫即可,這樣同時也對函式呼叫順序控制,從而達到類似洋蔥模型的執行效果。

    applyMiddleware 方法

    applyMiddleware函式形式

    構造閉包,為外部定義的方法,提供內部指定的apis;如下示例

    // 接受外部定義的方法陣列 funs
    function applyMiddleware(...funs){
      const apis = {
     dispatch: () => ({})
      }
      // 遍歷執行 外部定義的方法,構造一個閉包;並將返回值作為 結果返回
      return funs.map(fun => fun(apis))
    }

    被操作的funs函式形式

    為了構造一個閉包,fun 的返回值應該是一個使用了 apis 的函式。

    function myMiddleware1(apis){
      // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包,
      return actions => {
      // apis 方法呼叫
      const { dispatch } = apis
     dispatch(actions)
      }
    }
    const myMiddlewareWrappers = applyMiddleware(myMiddleware1)
    // myMiddlewareWrappers 為 [actions => { const { dispatch } = apis; dispatch(actions);}] 

    可能會有疑問:既然只是 apis 內的函式呼叫,為什麼要這麼複雜,藉助閉包實現?呼叫者直接通過呼叫 apis 內部的函式不就能達到目的?

  3. 閉包的應用:

    1. 閉包可以使我們在函式外部控制函式內部的執行邏輯,如常見的防抖、截流函式
    2. 利用閉包可以實現函式柯里化,從而達到函式入參快取的效果,進而達到一種單例的效果
  4. 為什麼不直接呼叫 apis 內部的方法:

    1. apis 內部的方法並不是一開始就存在的,可能是動態生成的,通過閉包可以起到1.b 所提到的單例效果
    2. 通常我們並不是單純的函式呼叫,往往附帶著額外的操作,不應用閉包會導致以下效果
    function fun1(arg){
      console.log(arg)
      apis.dispatch(arg)
    }
    function fun2(arg){
      arg.b = 100
      apis.dispatch(arg)
    }
    // apis 方法的呼叫可能會隨處可見,但是仔細觀察,apis 方法的呼叫其實都是重複的程式碼
    對比應用閉包的效果
    // 沒有重複的 apis 內部函式呼叫,構造的閉包函式會自動執行 apis 內部的方法
    // apis 暴露了但是沒有完全暴露
    const results = applyMiddleware(myMiddleware1,myMiddleware1)
    results[0](100)
    results[1](200)

    結合

    如何結合 compose 與 applyMiddleware 兩個方法,得到魔法效果呢?
    首先根據以上定義的 myMiddleware 形式的方法進行考慮,直接將 applyMiddleware 得到的 方法列表進行 compose

    const myMiddlewareWrappers = applyMiddleware(myMiddleware1, myMiddleware2)
    // 通過 compose 將 方法列表組合為一個方法
    const result = compose(...myMiddlewareWrappers)
    // 此時 result 為何種形式呢?
    // 根據 compose 的 作用,不難想象 result 為一個函式
    // actions => myMiddleware1(myMiddleware2(actions))

    到這裡,已經將 compose 與 applyMiddleware 結合起來了。
    但是仔細觀察 myMiddleware1、2 函式的形式,會發現一些問題,myMiddleware1、2方法返回值不是一個函式,經過開篇對compose的分析可以得到,這種形式會帶來函式之間 耦合嚴重 的問題。因此需要對myMiddleware進行一步改造,如下

    function myMiddleware3(apis){
      // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包,
      // 同時為了 保證 compose 過程中 每個 函式的 入參仍然為 一個函式,需要將以下返回值再
      // 進行一層封裝
      return next => actions => {
     // ...
     next(actions)
     // ...
      }
    }

    魔法

    但是我們還是不滿足以上的結果,apis 內部函式的呼叫不在函式呼叫者手裡,即,函式呼叫者只能傳入一個 actions 引數,而無法控制apis 函式的具體執行時機。如何達到控制反轉的效果?
    actions 其實可以是一個方法,此時就可以通過傳參的方式將 apis 再傳遞給 actions,從而將 apis 控制權交給真正的函式呼叫者。如下

    function myMiddleware2(apis){
     // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包,
     return next => actions => {
       // 控制反轉
       actions(apis)
     }
      }
    
      // 可以 結合myMiddleware2 與myMiddleware1 兩種形式的函式實現相容
      // 即 呼叫者需要 控制 apis 時通過 傳入函式形式的 actions 即可,否則傳入 apis 中指定形式入參即可
      function myMiddleware2(apis){
     // 返回一個 方法,該方法中會呼叫 apis 中暴露的方法,形成一個閉包,
     return next => actions => {
       // 控制反轉
       if(typeof actions === 'function') return actions(apis);
       next(actions)
     }
    }

相關文章