Redux action type 为什么必须用字符串Symbol 类型什么时候允许【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux在写 Redux 应用时store.dispatch()传入的 action 必须带一个type字段而且这个字段必须是字符串。不少开发者会问用Symbol做 type 可以避免字符串冲突为什么 Redux 不允许本文基于 redux 仓库的官方文档、源码和测试回答两个问题action.type为什么必须是字符串以及 Symbol 这类不可序列化值在什么条件下才允许出现在 action 中。为什么type必须是字符串官方 FAQ docs/faq/Actions.md 给出的直接原因是序列化As with state, serializable actions enable several of Reduxs defining features, such as time travel debugging, and recording and replaying actions. Using something like aSymbolfor thetypevalue or usinginstanceofchecks for actions themselves would break that.也就是说Redux 的标志性能力——时间旅行调试、录制并回放 action 序列——都建立在 action 可序列化serializable之上。Symbol无法通过JSON.stringify之类的标准方式序列化用它会直接破坏这些能力。docs/usage/ReducingBoilerplate.md 的表述与之一致推荐用字符串而不是 Symbol 定义 action type因为字符串可序列化而用 Symbol 会让录制和回放变得比必要的更困难。字符串还有第二个工程好处字符串是可自描述的。action type 会以原始文本形式出现在 Redux DevTools 的操作历史里团队在排查问题时读的就是这些字符串。Redux 实际强制执行的是什么FAQ 同时说明了 Redux不会强制执行整个 action 可序列化出于性能考虑Redux 无法可靠地检查序列化因此它只检查两件事见 docs/faq/Actions.md每个 action 必须是一个 plain objecttype字段必须是字符串。这两个检查可以在源码 src/createStore.ts 的dispatch实现中逐条对应function dispatch(action: A) { if (!isPlainObject(action)) { throw new Error( Actions must be plain objects. Instead, the actual type was: ${kindOf( action )}. You may need to add middleware to your store setup to handle dispatching other values, such as redux-thunk to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples. ) } if (typeof action.type undefined) { throw new Error( Actions may not have an undefined type property. You may have misspelled an action type string constant. ) } if (typeof action.type ! string) { throw new Error( Action type property must be a string. Instead, the actual type was: ${kindOf( action.type )}. Value was: ${String(action.type)} (stringified) ) } // ... 调用 reducer 并通知订阅者 }对应的完整错误文案可以在 errors.json 中查到编号 7、8、17 分别对应非 plain object、type为undefined、type不是字符串。也就是说type 用 Symbol、数字、布尔值都会直接抛错而 type 缺失undefined会被单独报出错误信息里提示你可能把 action type 字符串常量拼错了——这正是后文常量用法要防的场景。动手验证字符串能过Symbol 会抛错下面是一个可以直接跑的最小示例。前提是在自己的项目里安装redux包如npm install redux然后创建一个文件放入以下代码。文档同时提示核心包的createStore已被标记为不推荐官方建议改用reduxjs/toolkit的configureStore如果想保留createStore且不看到弃用提示可以改用legacy_createStore导入见 src/createStore.ts 中的注释说明。import { createStore } from redux // reducer 通过字符串比较 action.type function counter(state 0, action) { if (action.type counter/increment) return state 1 return state } const store createStore(counter) // 1. 字符串 type正常 dispatchreducer 生效 store.dispatch({ type: counter/increment }) console.log(store.getState()) // 文档示例1 // 2. Symbol typedispatch 时抛出错误 store.dispatch({ type: Symbol(MY_ACTION) })第 2 步会抛出Error: Action type property must be a string. Instead, the actual type was: symbol. Value was: Symbol(MY_ACTION) (stringified)仓库的测试 test/createStore.spec.ts 中对同一场景做了断言可作为预期行为参考以下均为测试中的期望值非运行日志// 来自 test/createStore.spec.ts 的 throws if action type is not string expect(() store.dispatch({ type: false })) .toThrow(/the actual type was: boolean.*Value was: false/) expect(() store.dispatch({ type: 0 })) .toThrow(/the actual type was: number.*Value was: 0/) expect(() store.dispatch({ type: null })) .toThrow(/the actual type was: null.*Value was: null/) expect(() store.dispatch({ type: })).not.toThrow() expect(() store.dispatch({ type: Symbol(MY_ACTION) })) .toThrow(/the actual type was: symbol.*Value was: Symbol\(MY_ACTION\)/)注意其中一个边界空字符串是字符串能通过检查测试中明确断言.not.toThrow()。Redux 的运行时检查不会替你拦截拼错的空 type所以命名约定见下节比运行时检查更可靠。如果不想自己写检查Redux 还导出了isAction工具函数docs/api/utils.md 说明它返回参数是否为合法 Redux action 对象带字符串type字段的 plain object同时可作为 TypeScript 类型谓词把类型收窄为Actionstring适合在中间件或工具代码里做防御性判断。Symbol 什么时候允许中间件拦截场景标题里的第二个问题——Symbol 什么时候允许——答案在 docs/faq/Actions.md 里有一段明确豁免Note that itisokay to use Symbols, Promises, or other non-serializable values in an action if the action is intended for use by middleware. Actions only need to be serializable by the time they actually reach the store and are passed to the reducers.docs/style-guide/style-guide.md 也给出同样的例外说明不要把 Promise、Symbol、Map/Set、函数或类实例放进 state 或 dispatched actions例外是——如果该 action 会在到达 reducers 之前被中间件拦截处理中间件如redux-thunk、redux-promise就属于这一类。所以判断标准是一条明确的边界action 会到达 reducerstype必须是字符串整个 action 应保持可序列化这样录制回放和 DevTools 才可用action 只给中间件消费、由中间件转译成普通字符串 action 再下发此时可以在 action 里放 Symbol、Promise 等非序列化值因为它们永远到不了 store。createStore的dispatch注释也描述了这条链路src/createStore.ts基础实现只支持 plain object action要 dispatch Promise、Observable、thunk 等值需要用对应的中间件包装 store即使加了中间件最终到达 store 的仍是 plain object action。字符串 type 的写法与维护建议必须用字符串不等于到处手写字面量。docs/usage/ReducingBoilerplate.md 给出了集中定义字符串常量的理由所有 action type 聚集在一处命名保持一致接手功能前可以先浏览已有的 action type 列表避免重复定义在 PR 中新增/修改的 action type 列表本身有助于团队把握改动范围导入常量时拼错会得到undefinedRedux 在 dispatch 这种 action 时会立即抛错就是上面undefined type那个错误能尽早发现拼写问题。FAQ 还提到把常量放进独立文件后可以用eslint-plugin-import这类工具检查import拼写从工具层面杜绝用错字符串。命名上官方教程 docs/tutorials/fundamentals/part-3-state-actions-reducers.md 的约定是type写成可读字符串通常取feature/eventName形式如todos/todoAdded让 type 字符串本身就能说明哪个功能、发生了什么。风格指南docs/style-guide/style-guide.md同时说明早期文档和示例多用ADD_TODO这类全大写常量名而现在更常用小写的todos/todoAdded形式团队按自身约定选择即可——Redux 对是否使用常量、如何命名没有强制意见。如果项目使用 Redux ToolkitcreateSlice会替你生成这些字符串action type 由 slice 的name和 reducer 函数名组合而成如counterincrement→counter/increment见 docs/tutorials/essentials/part-2-app-structure.md。另外需要注意版本行为docs/usage/migrations/migrating-rtk-2.md 指出从 Redux Toolkit 2 起store.dispatch(action)同样会强制action.type必须是字符串否则抛错——这与核心包的行为一致。小结一张判断表场景是否允许依据type为字符串action 到达 reducers必须如此src/createStore.ts 的dispatch检查type为Symbol且 action 到达 reducers抛错Action type property must be a string同上test/createStore.spec.tstype为undefined如常量导入拼错抛错并提示可能是拼写错误同上action 内含 Symbol/Promise 等非序列化值但由中间件先行拦截、不进入 reducers允许docs/faq/Actions.md、docs/style-guide/style-guide.md 的中间件例外记住这一条规则就覆盖了标题里的两个问题凡是会进入 reducers 的 actiontype用字符串并保持整体可序列化Symbol 只在中间件专属 action、不会到达 store这条路径上是合法选择。【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
