13

redux 是状态管理库,与其他框架如 react 是没有直接关系,所以 redux 可以脱离 react 在别的环境下使用。由于没有和react 相关逻辑耦合,所以 redux 的源码很纯粹,目的就是把如何数据管理好。而真正在 react 项目中使用 redux 时,是需要有一个 react-redux 当作连接器,去连接 reactredux

没看 redux 源码之前,我觉得看 redux 应该是件很困难的事情,因为当初在学 redux 如何使用的时候就已经被 redux 繁多的概念所淹没。真正翻看 redux 源码的时候,会发现 redux 源码内容相当之少,代码量也相当少,代码质量也相当高,所以是非常值得看的源码。

目录结构

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

.REDUXSRC
│ 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 来组合中间件,也是有那么一个点比较难理解。


李梦克
142 声望4 粉丝