Redux 源码分析

264 阅读5分钟

Redux是我们常用的一个状态管理库,经常会应用于结合react的项目开发中。最近研读了下Redux的源码,发现真的是非常的简短精炼啊,加上类型定义才一共一千多行的代码,相对于看庞大的react源码可以说是非常easy了。本着刨根问题的态度,对这个我经常性使用的库探究下源码实现。

(ps: Redux真的是深入贯彻了函数式编程理念) 我们从入口(index.ts)开始看:

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes, // 内置的一些actions
}
  1. 🌟createStore:顾名思义,创建一个store,并返回监听、派发store的一些方法。

  2. 🌟combineReducers: 将多个reducer合并为一个reducer

  3. bindActionCreators: 将action包裹一层dispatch方便直接调用

  4. 🌟applyMiddleware: 应用中间件

  5. compose: 将多个函数组合成一个函数的工具方法(在applyMiddleware中使用)

让我们一个一个分析:

createStore

先来个例子看看Redux最基本的用法:

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

let store = createStore(counterReducer)

// 监听store数据变化
store.subscribe(() => console.log(store.getState()))

// 触发动作
store.dispatch({ type: 'counter/incremented' }) // {value: 1}
store.dispatch({ type: 'counter/incremented' }) // {value: 2}
store.dispatch({ type: 'counter/decremented' }) // {value: 1}

哎?看看这subscribedispatch。这不就是我们非常熟悉的订阅发布模式嘛!

让我们来看看简化版CreateStore源代码(一些很少用的方法先不分析):

export default function createStore(reducer, preloadedState?, enhancer?){
  // 这个enhancer待会儿讲...
  if (typeof enhancer !== 'undefined') {
    return enhancer(createStore)(
      reducer,
      preloadedState
    )
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false
  
  // 返回当前state
  function getState() {
    return currentState
  }

  // 添加监听者
  function subscribe(listener) {
    nextListeners.push(listener)
     
    // 返回卸载监听者方法
    return function unsubscribe() {
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

  // 派发动作
  function dispatch(action) {
    // 获取新的state
    currentState = currentReducer(currentState, action)
    
    // 执行所有的监听者方法
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 创建完store后会派发默认的初始化动作,初始化状态
  dispatch({ type: ActionTypes.INIT } as A)

  const store = {
    dispatch: dispatch,
    subscribe,
    getState,
  } 
  return store
}

可以看到正如我们所想:通过subscribe方法我们可以注册监听者。当触发dispatch方法时,我们先根据action调用reducer来变更state,然后再通知到所有的监听者。

image.png

Redux的基本工作原理应该比较清晰了,那我们抛出几个问题:

  1. 为什么要叫reducer?

  2. enhancer是干嘛的?

  3. 中间件是怎么应用的?

  4. createStore参数只有一个reducer,一个reducer处理太多state的话太庞杂了,有没有什么办法能分割?

首先第一个问题:为什么要叫reducer?

是不是跟我们常用的js中Arrayreduce方法有点眼熟。官方说:

“ A function that returns the next state tree, given the current state tree and the action to handle.”

然后我们看看Array.reduce一般是咋用的:

[1,2,3,4,5].reduce((acc, cur) => acc + cur) // 15

再看看reducer函数:

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

我们类比一下:其实reducer可以看作是函数式编程中演绎高阶函数:reduce,中的那个函数参数。 acc等价于state,cur等价于action,函数返回操作后的新的state。因此叫做reducer。

第二个问题:enhancer是干嘛的?

createStore中有一个参数enhancer(增强器),顾名思义用于增强redux的能力。其本质是一个高阶函数,它的参数是redux的createStore函数,返回一个功能更强大的enhanced createStore

一般我们是这么使用的:

function enhancerCreator() {
  return createStore => (reducer, initialState, enhancer) => {
    // 先执行旧的createStore
    let store = createStore(reducer, initialState, enhancer);
    // 对旧的store做一些处理,并返回新的store。最常见的是处理dispatch函数,加一些劫持处理
    function dispatch(...args){
         console.log(args);
         return store.dispatch(...args);
    };
    return {...store, dispatch}
  }
}

const store = createStore(
  reducer,
  enhancerCreator()
);

看到这我们可能会有一个问题,因为我们都知道redux中间件其实就是劫持dispatch过程,并加入一些操作。那这个enhance和applyMiddleware有啥关系?

其实applyMiddleware就是一个enhancerCreator

const store = createStore(
  reducer,
  applyMiddleware([...middlewares])
);

applyMiddleware

Creates a store enhancer that applies middleware to the dispatch method of the Redux store. This is handy for a variety of tasks, such as expressing asynchronous actions in a concise manner, or logging every action payload.

那其实applyMiddleware的代码就比较清晰了:

export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore) => (reducer, preloadedState?) => {
      const store = createStore(reducer, preloadedState)
      
      const middlewareAPI: MiddlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }
      
      // #标记开始
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
      // #标记结束
      
      return {
        ...store,
        dispatch
      }
    }
}

重点在于标记部分代码。

我们知道一个中间件基本上是下面这个样子:

/**
 * 我们知道 type Dispatch<T> = (action: T, ...extra) => T 
 * 那下面中间件类型可以简化为:(api):(next: Dispatch) => Dispatch
 */
const  middleware = (api) => (next: Dispatch) => (action) => {
    // do something
}

标记部分的chain = ...那一行其实是给每一个中间件(高阶函数)赋api的值。赋值后chain中的每一个函数类型都是type ChainItem = (next: Dispatch) => Dispatch。是不是很眼熟,传入一个dispatch,返回一个新的dispatch,跟上文的enhance是很类似的模式。这也是我们非常常见的中间件模式(管道模式的一种实现),典型的洋葱模型。

第二行的dispatch = ...其实就是利用compose函数将这些方法组合起来

function compose(...funcs: Function[]) {
  return funcs.reduce((a, b) => (...args: any) => a(b(...args))
}

image.png

combineReducers

Turns an object whose values are different reducer functions, into a single reducer function. It will call every child reducer, and gather their results into a single state object, whose keys correspond to the keys of the passed reducer functions.

combineReducers其实就是将多个reducer组合成一个,这样用户编程时就可以拆分成多个reducer处理。

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers: ReducersMapObject = {}
  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)

  // 整合后的reducer
  return function combination(
    state = {},
    action: AnyAction
  ) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
 
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

我们使用的时候,看起来就像分割成了多个reducer,但是本质上还是一个~

const store = createStore(
  combineReducers({
      reducer1: reducer1,
      reducer2: reducer2,
  }),
);

后记

单看redux源码的话是比较简单的,实现的很精炼,其中对函数式编程的应用也很透彻。尤其其中的监听者模式的实现以及中间件模式的实现,还是很值得学习的。

不过redux只是一个基石,在redux之上我们还有很多工具:比如提供了更便捷api和很多的中间件的redux-tookit,再比如和react结合使用的react-redux,也是值得一探,就留给大家自己探索啦。