redux compose 详解

前言

redux compose 用于 applyMiddleware / createStore enhancer, 理解 compose 原理对于理解 redux 很有必要.

Src

https://github.com/reactjs/redux/blob/v3.7.2/src/compose.js

1
2
3
4
5
6
7
8
9
10
11
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)))
}

Example - middleware

拿 redux 自己的 middleware 举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []

const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)

return {
...store,
dispatch
}
  • 一个 middleware 原型为 store => next => action => { ... }
  • chain = middleware.map 这里, 传了 store = middlewareAPI, 成员原型为 next => action => {...}
  • dispatch = compose(...chain)(store.dispatch)

compose(...chain)

1
2
3
4
// 假设 chain.length = 3
= function() {
return chain0(chain1(chain2(...arguments)))
}

执行 compose(...chain)(store.dispatch)

因 middleware 中 action => {…} 部分和 store.dispatch 原型一致, 称为一个 middleware 的 dispatch 部分

  • chain[2] 即最后一个 item, next 参数 = store.dispatch , 返回一个 action => {...}, 称为 chain[2] 的 dispatch 部分
  • chain[1] 倒数第二个 item, next 参数 = chain[2] dispatch 部分, 返回 action => {...} , 成为 chain[1] 的 dispatch 部分
  • chain[0] 第一个 item, next 参数 = chain[1] dispatch 部分, 返回 action => {...} , 称为 chain[0] 的 dispatch 部分

最后 compose(...chain)(store.dispatch)

  • 结果是被 compose 的 chain[0] 的 dispatch 部分
  • 每一个 middleware 中的 next 是下一个 middleware 的 dispatch 部分
  • 最后一个 middleware 的 next 是 redux store 的 dispatch

心得

如果有一堆有相同目的的方法需要执行, 可以使用 redux compose 将其串起来, 对外暴露一个唯一入口

例如 [fn1, fn2, fn…] 有相同的原型, 需要串起来, 可以这样做

1
2
3
4
5
6
7
fn = compose(...[
next => fn1,
next => fn2,
next => ...
])

fn(fn_default) // fn_default 作为 fn... 最后一个的 next 参数

实战

例如 vue-router hook 中, 如 beforeEach, 如果要很多不想关的事情要做, 可以这样做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const thing1 = composeNext => (to, from, next) => {
// something sync
somethingSync()

composeNext(to, from, next)
}

const thing2 = composeNext => (to, from, next) => {
// something async
someThingAsync(function(){
composeNext(to, from, next)
})
}

const thing3 = composeNext => (to, from, next) => {
// blabla
composeNext(to, from, next)
}

// vuex router hook 必须要调一次 next
const defaultThing = (to, from, next) => next()

const beforeEach = compose(...[
thing1,
thing2,
thing3,
])(defaultThing)

// vue-router
router.beforeEach(beforeEach)

// 这样thing1, thing2, thing3 通过 composeNext 串联起来
// 调用 comoseNext(to, from, next) 就是执行下一件 thing
// 这样的逻辑写在一个 function, 在包含异步的情况下, 分分钟流程出错...

有用的链接