Redux 源码解析(二): bindActionCreators & combineReducers API

Redux 源码解析(二): bindActionCreators & combineReducers API

前言

今天是 Redux 源码解析的第二篇,本篇要介绍两个 API:bindActionCreators、combineReducers

正文

1. bindActionCreators 源码解析

用过 Redux 的应该知道,所谓的 bindActionCreators 就是为使用的 ActionCreator 自动绑定 dispatch 方法,使我们调用的时候会自动调用 dispatch,而不需要写成 dispatch(createAction()) 的麻烦形式

1.1 类型定义

首先我们先来看到与 bindActionCreators 相关的类型定义

  • /src/types/actions.ts(阅读笔记:/src/types/actions.ts

首先是 Action 相关的,我们知道通常我们使用的时候如下

// 使用示例
dispatch({ type: 'xxxType', payload: {} })

实际上官方类型定义里面只写明要有 type,而并没有规定一定用 payload,只是定义成可索引属性如下

export interface Action<T = any> {
  type: T
}

export interface AnyAction extends Action {
  // Allows any extra properties to be defined in an action.
  [extraProps: string]: any
}

同时 Redux 推荐我们使用一种所谓的 ActionCreator,平常使用的时候我们是这样写的

// 使用示例
const doSomething = (params) => ({
  type: 'someAction',
  payload: {}
})

很棒的是 Redux 也提供了相关的类型定义

export interface ActionCreator<A, P extends any[] = any[]> {
  (...args: P): A
}

export interface ActionCreatorsMapObject<A = any, P extends any[] = any[]> {
  [key: string]: ActionCreator<A, P>
}

第一种是单个 ActionCreator、第二种 ActionCreatorsMapObject 则是一个关于 ActionCreator 的映射表

1.2 导出方法签名

看完几个基本类型定义之后,我们下面来看看 bindActionCreators 有哪些导出的方法签名

  • /src/bindActionCreators.ts(阅读笔记:/src/bindActionCreators.ts/exports.ts
import { Dispatch } from './types/store'
import {
  AnyAction,
  ActionCreator,
  ActionCreatorsMapObject
} from './types/actions'
import { kindOf } from './utils/kindOf'

export default function bindActionCreators<A, C extends ActionCreator<A>>(
  actionCreator: C,
  dispatch: Dispatch
): C;

export default function bindActionCreators<
  A extends ActionCreator<any>,
  B extends ActionCreator<any>
>(actionCreator: A, dispatch: Dispatch): B;

export default function bindActionCreators<
  A,
  M extends ActionCreatorsMapObject<A>
>(actionCreators: M, dispatch: Dispatch): M;

export default function bindActionCreators<
  M extends ActionCreatorsMapObject,
  N extends ActionCreatorsMapObject
>(actionCreators: M, dispatch: Dispatch): N;

我们看到一共重载了四种方法签名,但是实际上参数就是分成 (actionCreator, dispatch)(actionCreators, dispatch) 两种,其他只是 TS 类型上细微的差异

下面我们就来看看实现

1.3 源码实现

  • /src/bindActionCreators.ts(阅读笔记:/src/bindActionCreators.ts/implements.ts

对于 bindActionCreators 的实现,其实要分成两种

第一种是实现单个 actionCreator 的绑定

function bindActionCreator<A extends AnyAction = AnyAction>(
  actionCreator: ActionCreator<A>,
  dispatch: Dispatch
) {
  return function (this: any, ...args: any[]) {
    return dispatch(actionCreator.apply(this, args))
  }
}

这里的实现就是为 actionCreator 套上一层壳,从 actionCreator() 变成 dispatch(actionCreator())

第二种则是 bindActionCreators 的完整实现,需要分别处理单个 actionCreator 和多个 actionCreator 两种场景

首先如果第一个参数是 function,则返回 bindActionCreator 的结果

export default function bindActionCreators(
  actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
  dispatch: Dispatch
) {
  // bindActionCreators(fn, dispatch)
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

否则则视为 ActionCreatorsMapObject 类型,将所有 key 对应的 actionCreator 分别绑定后返回

  // bindActionCreators(fnMap, dispatch)
  const boundActionCreators: ActionCreatorsMapObject = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

也就是说我们使用的时候可以如下两种用法

// 单个 creator
bindActionCreators(creator, dispatch)

// 多个 creator
bindActionCreators({ creatorA, creatorB, creatorC }, dispatch)

以上对于 bindActionCreators 的源码解析

2. combineReducers 源码解析

我们要介绍的第二个 API 则是 combineReducers

2.1 使用背景

一般情况下,我们创建 store 的时候可以创建一个 reducer,如下所示

const reducer = (state, action) => {}

const store = createStore(reducer)

然而当我们的 state 的数据结构足够复杂,同时我们希望区分为不同的模块的时候,我们则可以使用 combineReducers 来粘合多个 reducer

const rootReducer = combineReducers({ reducerA, reducerB, reducerC })

const store = createStore(rootReducer)

2.2 类型定义

稍微理解怎么用之后,我们就可以来看看 Redux 中定义的一些基础类型

  • /src/types/reducers.ts(阅读笔记:/src/types/reducers.ts

首先是基础的单个 reducer 的类型定义

export type Reducer<S = any, A extends Action = AnyAction> = (
  state: S | undefined,
  action: A
) => S

签名是接受状态 S 和操作 A 类型之后,返回新的状态 S

第二个则是我们刚刚提到的使用 combineReducers 时传入的参数是一个 reducer 的映射表,key 表示了每个 reducer 对应的 store.getState() 下的模块名称

export type ReducersMapObject<S = any, A extends Action = AnyAction> = {
  [K in keyof S]: Reducer<S[K], A>
}

如上,所谓的 ReducersMapObject 的 key 是大状态 S 的某个键 K,然后对应的 Reducer 类型为状态 S[K]

接下来有几个衍生类型,平常是比较少遇到的

export type StateFromReducersMapObject<M> = M extends ReducersMapObject
  ? { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
  : never

export type ReducerFromReducersMapObject<M> = M extends {
  [P in keyof M]: infer R
}
  ? R extends Reducer<any, any>
    ? R
    : never
  : never

StateFromReducersMapObject 表示的是 ReducersMapObject 所对应的 State 类型

有关于 State 类型的定义,当然也有 Aciton 的定义咯

export type ActionFromReducer<R> = R extends Reducer<any, infer A> ? A : never

export type ActionFromReducersMapObject<M> = M extends ReducersMapObject
  ? ActionFromReducer<ReducerFromReducersMapObject<M>>
  : never

2.3 导出方法签名

看完基础类型定义之后,combineReducers 的方法签名也呼之欲出了

  • /src/combineReducers.ts(阅读笔记:/src/combineReducers.ts/exports.ts
import { AnyAction, Action } from './types/actions'
import {
  ActionFromReducersMapObject,
  Reducer,
  ReducersMapObject,
  StateFromReducersMapObject
} from './types/reducers'
import { CombinedState } from './types/store'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
import warning from './utils/warning'
import { kindOf } from './utils/kindOf'

export default function combineReducers<S>(
  reducers: ReducersMapObject<S, any>
): Reducer<CombinedState<S>>;

export default function combineReducers<S, A extends Action = AnyAction>(
  reducers: ReducersMapObject<S, A>
): Reducer<CombinedState<S>, A>;

export default function combineReducers<M extends ReducersMapObject>(
  reducers: M
): Reducer<
  CombinedState<StateFromReducersMapObject<M>>,
  ActionFromReducersMapObject<M>
>;

一共有三种方法签名,不过差别都是在于 状态 S操作 A 等的类型定义,实际上三个重载的参数都一样,都是传入一个 reducers 对象

所以接下来我们就来看看 combineReducers 实现

2.4 源码实现

  • /src/combineReducers.ts(阅读笔记:/src/combineReducers.ts/implements.ts
export default function combineReducers(reducers: ReducersMapObject) {
  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)

在实现方法中的前半,我们可以看到一开始的工作是先做出一个确保属性都是 function 的 finalReducers 以及其对应的键值 finalReducerKeys

第二步则是对 reducers 进行校验,也就是试着传入几个特定的 Action 来测试返回的值

export default function combineReducers(reducers: ReducersMapObject) {
  // ...

  let shapeAssertionError: Error
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
function assertReducerShape(reducers: ReducersMapObject) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      // throw error
    }

    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      // throw error
    }
  })
}

也就是说 assertReducerShape 的作用就是对每个 reducer 进行初始化,然后检查返回的 State 是否符合预期

combineReducers 的函数主体到这里就完成了,最后返回一个合并后的 reducer,当然也必须符合 Reducer 的类型定义

export default function combineReducers(reducers: ReducersMapObject) {
  // ...
  
  return function combination(
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

函数的开头一样也是一些异常检查和报错提示

然后我们看到核心的部分

export default function combineReducers(reducers: ReducersMapObject) {
  // ...

  return function combination(
    state: StateFromReducersMapObject<typeof reducers> = {},
    action: AnyAction
  ) {
    // ...
    let hasChanged = false
    const nextState: StateFromReducersMapObject<typeof reducers> = {}
    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)
      if (typeof nextStateForKey === 'undefined') {
        // throw error
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

combine 之后的 reducer 被执行的时候实际上就是做这样的一个工作:

遍历一边 finalReducers

    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

最后返回之前检查是否所有 reducer 都没有变化决定直接返回旧的值

    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state

如此一来 combineReducers 的工作就完成了,对外部来说 combineReducers 返回的 reducer 与一般的 reducer 不同的是他返回的 state 状态多了一层罢了,而这个层级关系还是与 reducer 被合并的时候的结构相关的,也就是说当我使用如下的 reducer 的时候

const rootReducer = combineReducer({
  reducerA,
  reducerB: combineReducer({
    reducerB1,
    reducerB2,
  }),
  reducerC,
})

我就可以预期返回的 state 应该存在如下结构

{
  reducerA: {},
  reducerB: {
    reducerB1: {},
    reducerB2: {},
  },
  reducerC: {},
}

结语

本篇作为 Redux 源码解析的第二部,介绍了 bindActionCreators 和 combineReducers 两个 API,也是使用 Redux 时使用频率非常高的两个 API 供大家参考。

下一篇将为大家带来 Redux 源码解析的最后一篇,介绍 compose 和 applyMiddleware 两个 API,并稍微说明一下 Redux 所定义的中间件模式与应用模式

其他资源

参考连接

TitleLink
bindActionCreators - Reduxhttps://redux.js.org/api/bindactioncreators
combinereducers - Reduxhttps://redux.js.org/api/combinereducers
Redux - Githubhttps://github.com/reduxjs/redux

阅读笔记参考

https://github.com/superfreeeee/Blog-code/tree/main/source_code_research/redux-4.1.1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值