Redux 源码解读一次性搞定

719 阅读9分钟

Redux 是一个前端状态容器,提供可预测的状态管理,由 Flux 演变衍生而来。 Redux 的一些核心概念和使用方法可以参考官方文档:redux.js.org/

OK,话不多说,直接开始。

阅读版本: 4.0.4

源码地址: github.com/reduxjs/red…

辅助工具: Webstorm、Xmind、Terminal

PS: Redux 源码使用 Typescript 编写,涉及到比较繁琐的类型扩展,泛型等,在解析代码中会适当省略掉,保持简洁。

贴在前面

Xmind 确实是一个好用的工具。

工具函数

先阅读工具函数,因为这是在其他的核心函数模块中会用到的东西,而且工具函数的实现方式与其他模块没有任何强关联关系,可以独立阅读。

actionsTypes

actionTypes 定义了一些 redux 内部使用的 action 类型,供 redux 内部使用。如未识别的 action初始化状态替换状态这些类型是不能用在日常开发过程中的

const randomString = () =>
  Math.random()
    .toString(36)
    .substring(7)
    .split('')
    .join('.')

const ActionTypes = {
  INIT: `@@redux/INIT${randomString()}`,
  REPLACE: `@@redux/REPLACE${randomString()}`,
  PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}

如上定义了 INITREPLACEPROBE_UNKNOWN_ACTION 三种类型。通过使用

@@redux 前缀】+【名字】+【随机数36 进制的前7位并使用 . 分割】

规则生成的内部使用的 action 类型,比如使用随机生成的 PROBE_UNKNOWN_ACTION 去断言测试传入的 reducer 是否正确处理未识别的 action。

isPlainObject

判断数据是不是一个普通对象

function isPlainObject(obj: any): boolean {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

这个函数可以说成通过判断原型链的长度来判断是不是个原生对象。使用了 Object.getPrototypeOf 这个 API。这个 API 等同于已经废弃的 __proto__ 属性。做了以下三步操作:

  1. 使用迭代深度遍历对象的原型链,找到最后的普通原型对象 S
  2. 获取 obj 的原型对象,A
  3. 判断 S === A

因为对于 JavaScript 普通对象而言,它的原型链只有一层,就是 Object。可以理解为对于普通对象 obj 满足:Object.getPrototypeOf(obj) === Object.prototypeObject.getPrototypeOf(Object.prototype) === null

warning

一个简单的发警告消息的方法

export default function warning(message: string): void {
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  try {
    throw new Error(message)
  } catch (e) {}
}

基本模块

createStore

顾名思义这个函数就是用来创建 store 的一个函数模块。由于这个函数本身包含几个部分以及一些函数体内的闭包,因此我拆成多个部分单独解释。

参数处理

参数由三个部分组成:

  • reducer reducer 集合
  • preloadedState 初始化 state
  • enhancer 这是一个缺省参数,必须是函数。用来自定义强化 createStore 方法,返回一个新的 createStore 方法。

// 如果第二个参数是函数,表示这个参数是 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    preloadedState = undefined
}

// 如果有 enhancer,则使用 enhancer 增强 createStore
if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    return enhancer(createStore)(reducer, preloadedState as PreloadedState<S>) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}

if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
}

内部数据

内部声明保存的数据


// 保存 state 和 reducer。state 经过处理会改变,reducer 不变
let currentReducer = reducer
let currentState = preloadedState as S

// 订阅消息用的监听
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners

// 标记是否正在 dispatch 调用过程中(准确说应该是 reducer 计算过程)。在dispatch 函数中会对这个值进行处理。在内部很多地方会使用这个变量去判断当前的状态。
let isDispatching = false

内部函数

  • getState

获取 state 的方法。

function getState(): S {
    return currentState as S
}

这个方法只是简单的返回当前的状态(当然还有判断 isDispatching 被我省略了)

  • subscribe subscribe 用来添加一个数据变化的监听器。

function subscribe(listener: () => void) {
    let isSubscribed = true
    
    ensureCanMutateNextListeners()
    nextListeners.push(listener)
    
    return function unsubscribe() {
        if (!isSubscribed) {
            return
        }
        isSubscribed = false
    
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
        currentListeners = null
    }
}

subscribe 函数的主要功能:

  1. 向 nextListeners 中添加 listener
  2. 返回一个方法从 nextListeners 中移除添加的 listener

除此之外,使用了一个 ensureCanMutateNextListeners 方法来避免在 dispatch 过程中进行订阅/取消订阅调用的一些问题。

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
    }
}

创建了一个 nextListeners 的浅层副本。相当于说,每次 dispatch 执行的 listener 都是之前保存的一份快照。在这个期间发生的订阅和这取消订阅不会影响执行。

  • dispatch

dispatch 用来触发 action 更新 state。

function dispatch(action: A) {
    try {
        // 修改 dispatch 状态
        isDispatching = true
        // 使用 reducer 处理状态
        currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 执行订阅的监听器
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
}

dispatch 做的事情非常简单,包括三件:

  1. 修改 isDispatching 的状态。使用 try-catch-finally,即 isDispatching 仅表示 reducer 函数处理数据的过程。
  2. 使用 reducer 函数和 action 处理当前的 state
  3. 执行 subscribe 添加的监听器
  • replaceReducer

替换 reducer。PS: 源码中这一段书写的 Typescript 类型扩展太复杂了,直接简写了。

function replaceReducer(nextReducer: Reducer): Store {  
    
    // 替换 currentReducer
    currentReducer = nextReducer
    
    // 触发一次 REPLACE 的 action
    dispatch({ type: ActionTypes.REPLACE } as A)
    
    // 返回 store
    return store
  }

触发的 REPLACE action 没有额外的操作,只是做了一次记录。在 combineReducer 中也只是开发环境下使用做了一次判断。

  • observable

这是一个留给 observable/reactive 库的一个通信或者操作接口。一个参照标准的实现 tc39

  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer: unknown) {
        function observeState() {
          const observerAsObserver = observer as Observer<S>
          if (observerAsObserver.next) {
            observerAsObserver.next(getState())
          }
        }
        observeState()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [?observable]() {
        return this
      }
    }
  }

需要一个实现了 next 方法的 observer 对象。

最后一点

dispatch({ type: ActionTypes.INIT } as A)

const store = ({
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable
} as unknown) as Store
return store

在创建 store 的时候会触发一个 INIT 的 action,然后返回创建的 store 对象。

Compose

compose 是一个比较通用的工具函数,用来组合从右到左的一系列单参数函数(最右边的函数可以使多参数)。最终返回一个函数。比如: compose(f, g, h) => (...args) => f(g(h(...args)))

function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    return <T>(arg: T) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}

使用 reduce 遍历,并将上一次的处理结果返回作为下一次的输入,每次执行返回结果都是一个函数。

applyMiddleware

在讲 createStore 这个模块的时候提到了这个函数的第三个参数 enhancer 用来增强 createStore。 现在要分析的 applyMiddleware 的作用就是创建一个 enhancer 将一系列中间件应用到 redux store 的 dispatch 方法上。

function applyMiddleware(...middlewares: Middleware[]): StoreEnhancer<any> {
    return (createStore: StoreCreator) => (reducer: Reducer, ...args: any[]) => {
        const store = createStore(reducer, ...args)
        // 声明一个空的未处理的dispatch
        let dispatch: Dispatch = () => {
            // 直接调用会报错
            throw new Error(....)
        }
        
        // 每个 middleware 函数的参数。
        // 这里比较有意思的是 dispatch 这个参数是在 compose(...chain) 这一步完成之后才有实际值。也就是说dispatch 在 middleare 这个外层函数的执行中是无意义的,仅作为参数传递。
        const middlewareAPI: MiddlewareAPI = {
            getState: store.getState,
            dispatch: (action, ...args) => dispatch(action, ...args)
        }
        
        // 依次执行每个 middleware 并存储结果到 chain
        const chain = middlewares.map((middleware) => middleware(middlewareAPI))
        // middleware 的执行结果是一个单参数函数。参数就是 dispatch,并返回新的 dispatch 方法。
        // 使用 compose 调用 dispatch chain 去增强 dispatch 方法。
        const dispatch = compose(...chain)(store.dispatch)
    }
    
    // 返回新的 store,仅覆盖 dispatch 方法
    return {
        ...store,
        dispatch,
    }
}

简单来说 applyMiddleware 就是一个生成器,最终对 dispatch 进行处理增强或者说覆盖原有的 store.dispatch 功能,然后返回新的 store,可以分为一下几个步骤解释:

  1. applyMiddleware 生成 enhancer
  2. middleare 生成 dispatch 的增强器
  3. dispatch 的增强器生成新的 dispatch 方法

因此我们在写 middleware 的时候会是一个多层嵌套的函数结构:

const testMiddleware = ({ getState, dispatch }) => (next) => (action) => {
    if (action.type === 'LOG') {
        console.log('记录一条日志')
    }
    next(action)
}

把匿名箭头函数换成一种具名函数的写法,比较容易看懂:

function testMiddleware({ getState, dispatch }) {

    // 返回一个 dispatch 创建函数
    return function dispatchCreator(oldDispatch) {
        // 返回新的 dispatch 函数
        return function newDispatch(action) {
            if (action.type === 'LOG') {
                console.log('记录一条日志')
            }
            oldDispatch(action)
        }
    }
}

combineReducers

combineReducers 是一个辅助工具函数,通过对象字面量的方式接收多个 reducer 组合成一个新的 reducer 函数。

function combineReducers(reducers: ReducersMapObject) {

    // 第一步先筛选出正确的 reducers 和 reducerKeys
    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 是否正确
    let shapeAssertionError: Error
    try {
        assertReducerShape(finalReducers)
    } catch (e) {
        shapeAssertionError = e
    }
    
    return function combination(
        state: StateFromReducersMapObject,
        action: AnyAction
    ) {
        if (shapeAssertionError) {
            throw shapeAssertionError
        }
        let hasChanged = false
        const nextState: StateFromReducersMapObject = {}
        
        // 遍历所有的 reducer
        // 通过 key 使用 reducer 处理对应的 state
        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 最终结果为 true
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        
        // 最后判断 reducer 中的 key 不一致是否和 state 中的 key 一致,不一致也表示已修改,使用 newState
        hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
        return hasChanged ? nextState : state
    }
    
}

测试你的 reducer 是否正确的断言方法

function assertReducerShape(reducers: ReducersMapObject) {
    Object.keys(reducers).forEach(key => {
        const reducer = reducers[key]
        const initialState = reducer(
            undefined,
            { type: ActionTypes.INIT }
        )
        
        // 报错。传入 undefined 应该返回一个不是 undefined 的 initialState
        if (typeof initialState === 'undefined') {
            throw new Error('...')
        }
        
        // 报错。警告你不要用私有的 @@redux/ 命名空间 actionType。你应该为任何未知的 actionType 返回 initialState
        // PS: 其实就是你在写 reducer 的 switch case 的时候 default 返回 initialState
        if (
            typeof reducer(
                undefined,
                {
                    type: ActionTypes.PROBE_UNKNOWN_ACTION()
                }
            ) === 'undefined'
        ) {
            throw new Error('...')
        }
}

combineReducers 解析完成。其实简单也可以很简单的理解:

  1. 通过 key 组合多个 reducers
  2. 遍历 reducers 执行每个 reducer 传入对应 key 值的 state
  3. 通过 key 组合每一个 reducer 修改后的 state
  4. 返回新的组合后的 state

bindActionCreator

bindActionCreator 也是一个辅助函数,就是一个值是 action creator 的对象和 dispatch 绑定到一起组合成一个值是可以直接调用触发 dispatch action 的对象。


// 绑定单个函数
function bindActionCreator<A extends AnyAction = AnyAction>(
  actionCreator: ActionCreator<A>,
  dispatch: Dispatch
) {
  
  // 返回一个直接调用的函数,
  return function(this: any, ...args: any[]) {
    return dispatch(actionCreator.apply(this, args))
  }
}

// 绑定多个函数组合的 Object
function bindActionCreators(
    actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
    dispatch: Dispatch
) {
    if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }
    
    const boundActionCreators: ActionCreatorsMapObject = {}
    
    // 遍历这个 actionCreators 对象依次调用
    for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
            boundActionCreators[key] = bindActionCreator(actionCreator,     dispatch)
        }
    }
    
    return boundActionCreators
}

这个函数的实现比较简单。其原理就是实现一个新的函数把 actionCreator 的执行结果传入 dispatch 执行。

总结

自此,redux 源码解析结束。第一次发,有问题请大佬斧正o(╥﹏╥)o。

消耗: 2杯咖啡、一天 + 一个没睡够的晚上。

接下来会继续解读一下 react-redux 的源码。