Redux源码浅析

713 阅读9分钟

最近一段时间公司比较闲下来,故抽空学了react + redux。在react方面,鉴于有Vue的经验,很多东西概念上还是很统一的,例如Virtual Dom props JSX等。区别在于react没有像Vue那样那么多的api,更多的是用纯粹的JavaScript去解决,在这一点我觉得我更喜欢react多些。

当然,react更加的函数式,对于state的变更一定要通过setState这一点可以看出,react对于state不允许有任何的副作用。而相对与react的简单,更困扰我的是redux,它更加函数式,从代码中,随处可见的高阶函数科里化以及让这个之前没有接触过函数式编程的我感到神奇的compose等。因为如此,它的异步操作更是与Vuex有着很大的差异,各种各样基于redux的异步处理工具如雨后春笋般冒出来,例如redux-sagaredux-observable以及官方的一个只有12行代码的redux-thunk

因为没有时间可以给我实战中去理解它,所以我选择通过阅读其源代码,来加深对redux的理解

主要的api

redux暴露出来的api并不多

  • createStore:接受reducers生成状态树
  • combineReducers:把多个reducer合成一个reducers
  • bindActionCreators: 封装多一层函数嵌套,规避使用dispatch
  • applyMiddleware:初始化中间件
  • compose:非常函数式的一个方法,从右到左把接收到的函数合成后的最终函数

src/indexjs里面能看到,redux就只有暴露这五个api,其中经常用到的也就 3-4 个。

createStore

先看一下createStore的参数

// src/createStore.js
/**
 * @param {Function} reducer
 * @param {any} [preloadedState] 初始化的state
 * @param {Function} [enhancer] 这里是一个中间件
 * @returns {Store} 返回的是状态树
**/

参数很简单,只有三个,一个是reducer,第二个是初始化state,在我们有定义reducer的初始化state的时候,这个参数是默认不传的,在这里,先把其忽略。第三个是applyMiddleware(...middlewares),是中间件

看下整体的代码 其中getState很简单,就不单独拎出来说,obervable用于观察者模式,平时几乎用不到,也不讲

// src/createStore.js
export default function createStore(reducer, preloadedState, enhancer) {
    // 前面的代码是在做参数的初始化
    if (typeof enhancer !== 'undefined') {
        // 这里是处理中间件的地方,先不看这里,后面看中间件的时候反过来看
        return enhancer(createStore)(reducer, preloadedState)
    }
    // ..    
    let currentReducer = reducer // reducer
    let currentState = preloadedState // state
    let currentListeners = [] // 初始化监听器
    let nextListeners = currentListeners
    let isDispatching = false


    function ensureCanMutateNextListeners() {
        // ..
    }
    function getState() {
        // 获取state的方法
        // getState极其简单,就不单独拎出来的,就返回了当前的state
        if (isDispatching) {
            // 抛出错误
        }
        return currentState
    }
    function subscribe(listener) {
        // 监听器,dispatch action的时候就会执行
        // ..
    }
    function dispatch(action) {
        // 分发action的唯一方法
        // ..
    }
    function replaceReducer(nextReducer) {
        // 替换 store 当前用来计算 state 的 reducer
        // ..
    }
    function observable() {
        // 这个方法在文档里也没有体现,貌似是给redux-obervable专门提供的吗还是?不懂
        // ..
    }
    // 这里dispatch一个init action进行初始化
    // 在 src/utils/actionTypes.js 里
    dispatch({ type: ActionTypes.INIT })
    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [?observable]: observable
    }
}

接下来一个个看它的方法

subscribe

为什么要先讲subscribe呢,这涉及到react-redux的连接

其实我并不了解react-redux,只知道它调用了subscribe开启了个监听器,并传入了一个listener

这段代码在react-redux里的src/utils/Subscription.js

trySubscribe() {
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange)
      this.listeners = createListenerCollection()
    }
}

在这里传入了一个onStateChange方法作为listener

然后再看回subscribe

// src/createStore.js
function subscribe(listener) {
    
    // ...
    let isSubscribed = true

    // 判断当前的listener是否与下一个listener一样
    ensureCanMutateNextListeners()
    // push到nextListeners
    nextListeners.push(listener)
    console.log(listener)
    // 返回一个方法,作为取消订阅
    return function unsubscribe() {
      // ...

        isSubscribed = false
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
    }
  }

dispatch

dispatch做的事情很简单,只是把reducers传入stateaction去执行然后返回来的新的state赋值到currentState,接着再执行通过subscribe的监听函数就完成了。

// src/createStore.js
function dispatch(action) {
if (!isPlainObject(action)) {
    // 如果不是春函数,抛错
}

if (typeof action.type === 'undefined') {
    // 没有 type 抛错
}

if (isDispatching) {
    // 正在分发其他action,抛错
}

try {
    // 进行分发
    isDispatching = true
    // currentReducer 就是我们传进来的reducers
    currentState = currentReducer(currentState, action)
} finally {
    isDispatching = false
}
// 执行监听函数
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
}
// 返回action
return action
}

对于如何执行的reducers,我们到后面combineReducers再看

replaceReducer

在文档里,replaceReducer是这样描述的

替换 store 当前用来计算 state 的 reducer。这是一个高级 API。只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。

在这里我也没用过,不知道具体用途,可是代码简单,就贴出来看看

// src/createStore.js
function replaceReducer(nextReducer) {
    // 更替reducers再dispatch REPLACE type
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }

combineReducers

对于reducers,如果只有一个reducer那好说,action进去然后switch case执行完就完事了。可是大部分情况我们需要combineReducers组合我们的多个reducer,接下来看下combineReducers的代码实现

// src/combineReducers.js
export default function combineReducers(reducers) {
  // 获取reducers的key
  const reducerKeys = Object.keys(reducers)
  // 最终生成的reducers
  const finalReducers = {}
  // 遍历生成reducers,剔除不是function的
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    // ...
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    // 判断是否有初始化state
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
  // 最后返回了一个combinenation
  // 在 dispatch 的时候,就执行这个函数
  // currentState = currentReducer(currentState, action)
  return function combination(state = {}, action) {
      // ...
  }
}

combineReducers做的事情也很简单,通过高阶函数,利用闭包剔除不合格的reducer返回一个combination,接下来,来看看combination做了些什么

combination

先回顾下dispatch里,是怎么调用combination

// src/createStore.js
currentState = currentReducer(currentState, action)

传入了state还有action

// src/combineReducers.js
function combination(state = {}, action) {
    // ...

    let hasChanged = false
    // 全新的数据
    const nextState = {}
    // 遍历reducers keys
    // 每一个reducer都会去执行一遍
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 拿到变更前的state
      const previousStateForKey = state[key]
      // 变更后的state
      const nextStateForKey = reducer(previousStateForKey, action)
      // ...
      // 插入到nextState
      nextState[key] = nextStateForKey
      // 判断是否有变化
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }

combination也很简单,就是遍历reduces并执行,再返回state。

到这里,整个dispatch的流程就可以清晰列出来了

dispatch => combination => iterator reducers => return new state

中间件的实现

redux里,可以通过applyMiddleware添加中间件,redux的代码非常简洁,需要复杂的功能,就需要通过中间件去加强。

### applyMiddleware

中间件的作用是,在dispatch做分发的时候,会按照在applyMiddleware时传入的中间件顺序,依次执行,到最后再做dispatch

先看看中间件的写法

// 回调给的getState, dispatch方法
// 一般dispatch不会使用
function someMiddleware ({ getState, dispatch}) {
  // next是下一个中间件
  // action就是dispatch的action
  return next => action => {
    // before dispatch
    // 执行下一个中间件
    let returnValue = next(action)
    // after dispatch
    // 一般会是 action 本身,除非后面的 middleware 修改了它。
    return returnValue
  }
}

在需要中间件去生成store的方式有两种

import { createStore, combineReducers, applyMiddleware } from 'redux'
// 这是第一种
const store = createStore(
  reducers,
  applyMiddleware(
    thunkMiddleware,
    logger,
    loggerMiddleware
  )
)

// 这是第二种
const store = applyMiddleware(
    thunkMiddleware,
    logger,
    loggerMiddleware
  )(createStore)(reducers)

两种方式并没有区别,还记不记得之前在createStore里,说先不看的那句代码

return enhancer(createStore)(reducer, preloadedState)

这里的enhancer就是applyMiddleware(...middlewares),看下applyMiddleware的代码

// src/applyMiddleware.js
function applyMiddleware(...middlewares) {
  // 返回一个参数为createStore的匿名函数
  // args就是需要传入的reduces还有init state
  return createStore => (...args) => {
    // 重新走一遍createStore,生成store
    const store = createStore(...args)
    let dispatch = function () {
      // 这里提供dispatch,可是只用来在中间件中dispatch报错
    }
    let chain = [] // 定义中间件的chain
    // 在中间件需要的两个方法
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => {
        console.log('DISPATCH')
        return dispatch(...args)
      }
    }
    // 把需要的getState以及dispatch方法传入中间件
    chain = middlewares.map(middleware => {return middleware(middlewareAPI)})
    // 这里通过compose生成一个dispatch函数
    // 这里还需要传入最初的dispatch,就是为了在中间件执行完毕只有,最后能成功dispatch action
    dispatch = compose(...chain)(store.dispatch)
    // 最后返回store的方法以及新的dispatch
    // 原来的dispatch被覆盖
    return {
      ...store,
      dispatch
    }
  }
}

compose

compose是干什么的呢?先看看一个在Ramda.js下的compose

import R from 'ramda'
let compse = R.compose(
    R.multiply(2),
    R.add(7),
    R.divide(3)
)
compose(9)
// 输出20

简单的说,compose是一个柯里化函数,将多个函数合并成一个函数,从右到左执行

在例子中,compose接受三个方法,从右到左就是 除以3 加7 以及乘以2。添加一个参数9,依次执行,最后输出的是20

我们先跳到applyMiddleware看他的执行

// src/applyMiddleware.js
chain = middlewares.map(middleware => {return middleware(middlewareAPI)})
dispatch = compose(...chain)(store.dispatch)

看看redux中的compose

// src/compose.js
export default function compose(...funcs) {
 // 判断funcs的数量
  if (funcs.length === 0) {
    return arg => arg
  }
  
  if (funcs.length === 1) {
    return funcs[0]
  }
  // 这里通过reduce,非常优雅地实现了compose
  return funcs.reduce((a, b) => {
    // 这里是compose(...chain)
    return (...args) => {
      // 这里传入store.dispatch,最终形成一个执行链
      return a(b(...args))
    }
  })
}

reduce

reduce方法是一个非常函数式的方法,在MDN上我们可以看到reduce的详情

reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。

看看reduce的参数,第一个是执行回调函数,直接贴上MDN的参数说明

callback 执行数组中每个值的函数,包含四个参数: accumulator 累加器累加回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue(如下所示)。 currentValue 数组中正在处理的元素。 currentIndex 数组中正在处理的当前元素的索引。 如果提供了initialValue,则索引号为0,否则为索引为1。 array 调用reduce的数组

arr.reduce(callback[, initialValue])

const total = [0, 1, 2, 3].reduce(function(sum, value) {
  return sum + value;
}, 0);
// log 6

到此,整个中间件就完成了,整理下中间件的流程,举例上面的例子

dispatch(action) => thunkMiddleware => logger => loggerMiddleware => store.dispatch

总结

从整体来说,redux是一个非常巧妙的库,它代码不多,随处散发着函数式编程的魅力。也是因为那个compose驱使着我去探索函数式编程。

作为一个浅入react开发的程序员,redux只是整个技术栈的一部分,希望接下来可以去探索一些异步流程库,例如redux-saga redux-observable等。