redux之compose

limengke123發表於2018-07-28

redux 是狀態管理庫,與其他框架如 react 是沒有直接關係,所以 redux 可以脫離 react 在別的環境下使用。由於沒有和react 相關邏輯耦合,所以 redux 的原始碼很純粹,目的就是把如何資料管理好。而真正在 react 專案中使用 redux 時,是需要有一個 react-redux 當作聯結器,去連線 reactredux

沒看 redux 原始碼之前,我覺得看 redux 應該是件很困難的事情,因為當初在學 redux 如何使用的時候就已經被 redux 繁多的概念所淹沒。真正翻看 redux 原始碼的時候,會發現 redux 原始碼內容相當之少,程式碼量也相當少,程式碼質量也相當高,所以是非常值得看的原始碼。

目錄結構

其他目錄都可以不看,直接看 ./src 吧:

.\REDUX\SRC │ applyMiddleware.js │ bindActionCreators.js │ combineReducers.js │ compose.js │ createStore.js │ index.js │ └─utils actionTypes.js isPlainObject.js warning.js

index.js 就是把 applyMiddleware.js 等彙集再統一暴露出去。utils 裡面就放一些輔助函式。所以一共就五個檔案需要看,這五個檔案也就是 redux 暴露出去的五個 API

// index.js
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

// 忽略內容

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}
複製程式碼

compose.js

這是五個 API 裡唯一一個能單獨拿出來用的函式,就是函數語言程式設計裡常用的組合函式,和 redux 本身沒有什麼多大關係,先了解下函數語言程式設計的一些概念:

純函式是這樣一種函式,即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用。 程式碼組合

程式碼:

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)))
}
複製程式碼

其實 compose 函式做的事就是把 var a = fn1(fn2(fn3(fn4(x)))) 這種巢狀的呼叫方式改成 var a = compose(fn1,fn2,fn3,fn4)(x) 的方式呼叫。

reduxcompose 實現很簡潔,用了陣列的 reduce 方法,reduce 的用法可以參照 mdn

核心程式碼就一句:return funcs.reduce((a,b) => (..args) => a(b(...args)))

我雖然經常寫 reduce 函式,但是看到這句程式碼還是有點懵的,所以這裡舉一個實際的例子,看看這個函式是怎麼執行的:

import {compose} from 'redux'
let x = 10
function fn1 (x) {return x + 1}
function fn2(x) {return x + 2}
function fn3(x) {return x + 3}
function fn4(x) {return x + 4}

// 假設我這裡想求得這樣的值
let a = fn1(fn2(fn3(fn4(x)))) // 10 + 4 + 3 + 2 + 1 = 20

// 根據compose的功能,我們可以把上面的這條式子改成如下:
let composeFn = compose(fn1, fn2, fn3, fn4)
let b = composeFn(x) // 理論上也應該得到20
複製程式碼

看一下 compose(fn1, fn2, fn3, fn4)根據 compose 的原始碼, 其實執行的就是: [fn1,fn2,fn3.fn4].reduce((a, b) => (...args) => a(b(...args)))

第幾輪迴圈 a的值 b的值 返回的值
第一輪迴圈 fn1 fn2 (...args) => fn1(fn2(...args))
第二輪迴圈 (...args) => fn1(fn2(...args)) fn3 (...args) => fn1(fn2(fn3(...args)))
第三輪迴圈 (...args) => fn1(fn2(fn3(...args))) fn4 (...args) => fn1(fn2(fn3(fn4(...args))))

迴圈最後的返回值就是 (...args) => fn1(fn2(fn3(fn4(...args))))。所以經過 compose 處理過之後,函式就變成我們想要的格式了。

總結

compose 函式在函數語言程式設計裡很常見。這裡 redux 的對 compose 實現很簡單,理解起來卻沒有那麼容易,主要還是因為對 Array.prototype.reduce 函式沒有那麼熟練,其次就是這種接受函式返回函式的寫法,再配上幾個連續的 => ,容易看暈。

這是 redux 解讀的第一篇,後續把幾個 API 都講一下。特別是 applyMiddleware 這個 API 有用到這個 compose 來組合中介軟體,也是有那麼一個點比較難理解。

相關文章