尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redux bindActionCreators 完全指南:将 action creator 批量绑定到 dispatch 的实现原理与实战用法

Redux bindActionCreators 完全指南:将 action creator 批量绑定到 dispatch 的实现原理与实战用法 Redux bindActionCreators 完全指南将 action creator 批量绑定到 dispatch 的实现原理与实战用法【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/reduxbindActionCreators是 Redux 提供的一个便捷工具函数它能把一组 action creator 包装成“调用即派发dispatch”的绑定函数从而让对 Redux 一无所知的下层组件无需接触dispatch与 store 也能触发状态变更。本文将以本仓库 docs/api/bindActionCreators.md 为骨架结合 src/bindActionCreators.ts 源码与 test/bindActionCreators.spec.ts 测试讲透它的适用场景、参数与返回值语义、边界行为及完整代码示例帮助你在 React 组件树中优雅地隔离 Redux 关注点。一、Overview它解决什么问题Redux 官方文档对bindActionCreators的定义是把一个“值均为 action creator 的对象”转换成另一个“键完全相同、但每个 action creator 都被包裹进一次dispatch调用”的对象从而可以直接调用这些函数来派发 action。bindActionCreators(actionCreators, dispatch)要理解这个 API 的价值先要区分两个基础概念详见 Glossaryaction描述“发生了什么”的纯对象必须包含type字段action creator创建 action 的工厂函数。调用 action creator 只会产出 action并不会派发它——真正触发状态变更必须调用 store 的dispatch。什么时候才需要它文档明确指出正常情况下你应该直接在你的 Store 实例上调用dispatch如果使用 Reactreact-redux 也会把dispatch注入到组件 props 中同样可以直接调用。bindActionCreators的唯一真正使用场景是你想把某些 action creator 传给一个对 Redux 完全无感知的组件同时又不想把dispatch或整个 store 传下去。此时你提前在“知道 Redux”的容器层把 action creator 与dispatch绑定好下层组件拿到的只是普通函数调用即派发。为了便利你也可以把单个 action creator 函数作为第一个参数传入返回的将是一个被dispatch包裹的单函数。一处重要警告文档特别标注了:::warning该 API最初是为旧版 react-redux 的connect方法设计的。它至今仍可工作但在现代 Redux 应用中已很少需要——随着 React-Redux 7.1 的 hooks API 普及useDispatch与useSelector已成为主流的取值与派发方式。二、Parameters 与 Returns精确的签名语义参数actionCreatorsFunction或Object一个 action creator或一个“值均为 action creator”的对象。文档提示获得这种对象最便捷的方式是import * as语法。dispatchFunctionStore 实例上可用的dispatch函数。返回值若第一个参数是对象返回一个模拟原对象结构的新对象——键保持一致但每个函数被替换为“立即派发对应 action creator 返回值”的绑定版本若第一个参数是函数返回的也是单个函数即被dispatch包裹后的同一函数。类型层面的定义在仓库的 src/bindActionCreators.ts 中函数提供了四组重载签名覆盖“单函数输入 / 对象输入”以及“保留精确类型 / 泛型映射”的组合export default function bindActionCreatorsA, C extends ActionCreatorA( actionCreator: C, dispatch: Dispatch ): C export default function bindActionCreators A extends ActionCreatorany, B extends ActionCreatorany (actionCreator: A, dispatch: Dispatch): B export default function bindActionCreators A, M extends ActionCreatorsMapObjectA (actionCreators: M, dispatch: Dispatch): M export default function bindActionCreators M extends ActionCreatorsMapObject, N extends ActionCreatorsMapObject (actionCreators: M, dispatch: Dispatch): N传入函数时返回类型为C保留原函数签名或B宽松映射传入对象时返回类型为M保留原对象结构或N映射后的对象结构。支撑这些签名的基础类型定义在 src/types/actions.tsexport interface ActionCreatorA, P extends any[] any[] { (...args: P): A } export interface ActionCreatorsMapObjectA any, P extends any[] any[] { [key: string]: ActionCreatorA, P }其中Dispatch接口定义于 src/types/store.ts表示“接受一个 action或其子类型并返回同一 action 的派发函数”。三、源码实现核心逻辑逐行拆解bindActionCreators的实现非常精简完整代码见 src/bindActionCreators.ts核心是内部辅助函数bindActionCreatorfunction bindActionCreatorA extends Action( actionCreator: ActionCreatorA, dispatch: DispatchA ) { return function (this: any, ...args: any[]) { return dispatch(actionCreator.apply(this, args)) } }它返回一个闭包函数调用时先用actionCreator.apply(this, args)执行原始 action creator 生成 action再立即交给dispatch派发并把dispatch的返回值按 Store 的约定通常是原 action 对象透传出去。主函数逻辑分三条路径export default function bindActionCreators( actionCreators: ActionCreatorany | ActionCreatorsMapObject, dispatch: Dispatch ) { if (typeof actionCreators function) { return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators ! object || actionCreators null) { throw new Error( bindActionCreators expected an object or a function, but instead received: ${kindOf( actionCreators )}. Did you write import ActionCreators from instead of import * as ActionCreators from? ) } const boundActionCreators: ActionCreatorsMapObject {} for (const key in actionCreators) { const actionCreator actionCreators[key] if (typeof actionCreator function) { boundActionCreators[key] bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }可以提炼出以下实现事实单函数快捷路径typeof actionCreators function时直接包装并返回单个绑定函数非法入参的防御既不是函数、也不是对象或为null时抛出错误错误信息使用kindOf实现于 src/utils/kindOf.ts报告实际收到的类型并附带一段贴心提示Did you write import ActionCreators from instead of import * as ActionCreators from?——这正对应了常见的“默认导入导致拿到对象而非函数模块”的错误写法非函数值静默跳过遍历对象时只有typeof actionCreator function的键才会被绑定进结果对象非函数值如数字、字符串、undefined、嵌套对象会被忽略对象结构保持结果对象的键与原对象一致便于调用方按同名属性取用。bindActionCreators是 Redux 公开 API 的一员从 src/index.ts 的导出清单可见它与createStore、combineReducers、applyMiddleware、compose一同被导出并在 docs/api/api-reference.md 的 API 索引中列出。四、完整示例向“不知 Redux”的组件传递绑定函数文档提供了一个非常完整的 React 实战示例我们将其原样继承并补充注释说明。TodoActionCreators.js定义 action creatorexport function addTodo(text) { return { type: ADD_TODO, text } } export function removeTodo(id) { return { type: REMOVE_TODO, id } }SomeComponent.js在容器层绑定并下传import React from react import { bindActionCreators } from redux import { connect } from react-redux import * as TodoActionCreators from ./TodoActionCreators console.log(TodoActionCreators) // { // addTodo: Function, // removeTodo: Function // } function TodoListContainer(props) { // Injected by react-redux: const { dispatch, todos } props // Heres a good use case for bindActionCreators: // You want a child component to be completely unaware of Redux. // We create bound versions of these functions now so we can // pass them down to our child later. const boundActionCreators useMemo( () bindActionCreators(TodoActionCreators, dispatch), [dispatch] ) console.log(boundActionCreators) // { // addTodo: Function, // removeTodo: Function // } useEffect(() { // Note: this wont work: // TodoActionCreators.addTodo(Use Redux) // Youre just calling a function that creates an action. // You must dispatch the action, too! // This will work: let action TodoActionCreators.addTodo(Use Redux) dispatch(action) }, []) return TodoList todos{todos} {...this.boundActionCreators} / // An alternative to bindActionCreators is to pass // just the dispatch function down, but then your child component // needs to import action creators and know about them. // return TodoList todos{todos} dispatch{dispatch} / } export default connect(state ({ todos: state.todos }))(TodoListContainer)这段代码演示了三个要点import * as TodoActionCreators得到的是一个“值均为 action creator”的对象这正是bindActionCreators第一个参数的标准形态绑定后的boundActionCreators拥有与TodoActionCreators相同的键但每个函数调用即派发——TodoList {...this.boundActionCreators} /把addTodo、removeTodo作为普通 props 传入子组件子组件无需 import action creator、更无需知道dispatch的存在作为对照useEffect中展示的“先let action TodoActionCreators.addTodo(Use Redux)再dispatch(action)”是未绑定时的标准手动写法——它强调了一个事实仅调用 action creator 不会改变任何状态。此外示例还给出了一个替代方案直接把dispatch传给子组件TodoList todos{todos} dispatch{dispatch} /但那样子组件就必须自己 import action creator 并知晓其存在破坏了隔离性。这正是bindActionCreators在“解耦组件与 Redux”上的意义。注意示例中useMemo(() bindActionCreators(TodoActionCreators, dispatch), [dispatch])是一个值得借鉴的细节——把依赖限定为dispatch避免每次渲染都重新生成绑定函数由于dispatch引用在 store 生命周期内稳定绑定结果可安全复用。五、边界行为与错误处理来自测试用例的验证仓库中的 test/bindActionCreators.spec.ts 用真实 storecreateStore(todos)todos reducer 定义于 test/helpers/reducers.tsaction creator 定义于 test/helpers/actionCreators.ts系统验证了该 API 的全部行为可作为实现语义的权威证据测试用例断言内容对应行为wraps the action creators with the dispatch function返回对象的键与原对象一致调用boundActionCreators.addTodo(Hello)后store.getState()变为[{ id: 1, text: Hello }]对象中的每个函数都被 dispatch 包装调用即触发状态变更wraps action creators transparently绑定函数与原 action creator 在相同this、相同参数下产生完全相等的 action包括this引用包装是“透明”的this与参数被原样透传对应actionCreator.apply(this, args)的实现skips non-function values in the passed object对象中混入foo: 42、bar: baz、wow: undefined、much: {}、test: null等非函数值时返回对象键仍只包含函数非函数值被静默跳过supports wrapping a single function only传入单个addTodo函数返回单个函数调用后 store 状态更新单函数快捷路径生效throws for an undefined actionCreator传入undefined抛错消息含received: undefined非法入参防御throws for a null actionCreator传入null抛错消息含received: null非法入参防御throws for a primitive actionCreator传入字符串抛错消息含received: string非法入参防御这些用例还印证了两个细节返回 action 而非 void测试中const action boundActionCreators.addTodo(Hello)的返回值与actionCreators.addTodo(Hello)完全相等——因为dispatch会返回被派发的 action见 src/types/store.ts 中Dispatch接口与Store.dispatch的文档注释绑定函数透传了它错误消息的调试价值undefined、null、string三种非法输入都会得到“bindActionCreators expected an object or a function, but instead received: 类型”的明确提示且错误消息来自 src/bindActionCreators.ts 的运行时校验类型信息由kindOf生成。六、实践建议何时用、何时不用综合文档与源码可以给出如下工程化建议默认直接用dispatch绝大多数场景下容器组件内直接dispatch(actionCreator(...))或在现代 React-Redux 中用useDispatch即可无需引入bindActionCreators解耦“非 Redux 感知”组件时使用当你需要把一个操作集合以普通函数 props 的形式下发给某个完全不知道 Redux 存在也不应 import action creator的组件时bindActionCreators是最契合的胶水——它把“创建 action”与“派发 action”两个关注点在你的容器层一次性缝合配合useMemo缓存在函数组件中使用时建议像文档示例那样用useMemo(() bindActionCreators(creators, dispatch), [dispatch])包裹避免重复创建绑定函数造成不必要的子组件重渲染与 hooks API 的关系react-redux 的useDispatch普及后该 API 在多数新代码中已非必需但它仍是对connect老代码进行维护与理解时绕不开的知识点migrating-to-modern-redux.mdx 中记录了向现代模式迁移时如何取舍此类模式。小结bindActionCreators的职责可以浓缩为一句话在“调用 action creator”和“派发结果 action”之间插入一层自动化的dispatch粘合。它不改变任何 Redux 状态管理机制只是一个纯函数级别的便捷封装其全部语义——单函数快捷路径、对象键保持、非函数值跳过、非法输入报错、this/参数透明透传——都能在 src/bindActionCreators.ts 约 30 行核心实现与 test/bindActionCreators.spec.ts 的 7 组用例中找到一一对应的证据。当你需要让深层的展示组件保持对 Redux 的无知时它就是那把最精准的钥匙。【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表