redux-form reducer 完全指南:在 Redux Store 中挂载与扩展表单状态
redux-form reducer 完全指南在 Redux Store 中挂载与扩展表单状态【免费下载链接】redux-formA Higher Order Component using react-redux to keep form state in a Redux store项目地址: https://gitcode.com/gh_mirrors/re/redux-formredux-form 是一个通过 react-redux 将表单状态保存在 Redux store 中的高阶组件库。在接入 redux-form 之前你必须在 Redux 中注册一个「表单 reducer」form reducer它是整个库运行的数据基石所有字段的values、fields元信息、同步/异步校验错误、提交状态submitting / submitFailed / submitSucceeded都存储在这个 reducer 所管理的状态切片中。本文将围绕 docs/api/Reducer.md 展开先介绍 redux-form 的reducer如何挂载到 Redux store 中ES5 / ES6 两种写法再说明getFormState如何适配非默认挂载位置接着讲解 Immutable.js 场景下必须使用redux-form/immutable的原因最后结合 createReducer.js 与 ReducerPlugin.md 剖析 reducer 的底层结构、action 分发机制以及reducer.plugin()的高级扩展能力。读完你将能够独立完成 redux-form 在项目中的初始化配置并根据业务需求自定义表单状态的响应逻辑。一、reducer 是什么redux-form 的状态基石reducer是 redux-form 导出的表单 reducer官方文档docs/api/Reducer.md明确要求将其挂载到 Redux 状态的form字段下The form reducer. Should be mounted to your Redux state atform.从源码看src/reducer.js 只有三行有效代码——它将默认的 plain普通 JavaScript 对象结构传入createReducer工厂函数并导出// flow import createReducer from ./createReducer import plain from ./structure/plain export default createReducer(plain)也就是说导出的reducer实际上是 src/createReducer.js 中createReducer(structure)的产物它由三层能力叠加而成见createReducer末尾的return decorate(byForm(reducer))reducer纯表单级 reducer根据 action 类型查表执行对应行为byForm(reducer)外层分发器根据 action 上的meta.form把动作路由到具体某个表单的状态切片state slice上decorate(target)为 reducer 挂载plugin()方法允许注入自定义 reducer 来响应任意 action。这种「工厂 分层装饰」的设计意味着reducer 并非一个硬编码的巨型函数而是由结构适配层structure驱动、可复用的通用实现——这也是它能同时支持 plain 对象与 Immutable.js 的原因。二、挂载到 Redux storeES5 与 ES6 两种写法ES5 写法var redux require(redux) var formReducer require(redux-form).reducer // 使用 Immutable.js 时改为 // var formReducer require(redux-form/immutable).reducer; var reducers { // ... your other reducers here ... form: formReducer } var reducer redux.combineReducers(reducers) var store redux.createStore(reducer)ES6 写法import { createStore, combineReducers } from redux import { reducer as formReducer } from redux-form // 使用 Immutable.js 时改为 // import { reducer as formReducer } from redux-form/immutable; const reducers { // ... your other reducers here ... form: formReducer } const reducer combineReducers(reducers) const store createStore(reducer)两点关键说明key 名必须是formcombineReducers中对象字面量的 keyform决定了 reducer 挂载的位置也就是默认的getFormState查找路径state.form。库内所有 selector 默认都会从state.form读取表单状态例如 src/createFormValueSelector.js 中的默认取法就是state getIn(state, form)。reducer 必须与你的 state 结构类型匹配普通 JS 对象用redux-form主入口Immutable.js 状态则必须用redux-form/immutable详见本文第四节。安装版本方面当前仓库 package.json 中 redux-form 的版本为8.3.10peer 依赖要求redux ^3.7.2 || ^4.0.0、react ^16.4.2 || ^17.0.0 || ^18.0.0、react-redux ^6.0.1 || ^7.0.0 || ^8.0.0可作为版本选型的参考。三、非默认挂载位置getFormState适配官方文档强调如果必须把 reducer 挂载到form之外的其他位置可以给reduxForm()装饰器提供一个getFormState(state)函数用来告诉 redux-form 从 Redux state 的哪个切片读取表单状态。import { reduxForm } from redux-form reduxForm({ form: myForm, getFormState: state state.someOtherSlice.form // 自定义挂载位置 })这条机制贯穿在 redux-form 的整个连接体系中。从源码可以验证src/ConnectedField.js、src/ConnectedFieldArray.js、src/ConnectedFields.js 都会从注入的_reduxForm上下文里取出getFormState然后以getFormState(state)的结果作为当前表单状态表单级 selector 同样遵循这一约定getFormState缺省时统一回退到state.form见 src/createFormValueSelector.js。因此「默认挂载在form」是最大公约数约定getFormState则提供了把表单状态放到任意嵌套位置的逃生通道。另外在 src/tests/reduxForm.spec.js 中还有「getFormState返回 undefined 时应正常工作」的防御性测试说明 redux-form 对异常返回值也有兜底处理。四、Immutable.js 场景为什么必须用redux-form/immutable官方文档给出的是一条硬性规则If youre using Immutablejs to manage your Redux state, you MUST import the reducer from redux-form/immutable.原因要从 reducer 的工厂实现说起。createReducer接收一个structure结构适配层参数src/structure/plain/index.js 与 src/structure/immutable/index.js 分别实现了两套能力plainredux-formimmutableredux-form/immutableempty{}Map()emptyList[]List()fromJS原样返回value value递归转成Map/ListgetIn/setIn/deleteIn基于路径字符串操作普通对象基于toPath操作 Immutable 集合allowsArrayErrorstrue支持数组级错误false不支持数组级错误toJS原样返回value.toJS()两套结构的splice、deepEqual、keys等操作也各自针对对象与 Immutable 集合实现。由于 reducer 内部如INITIALIZE时的fromJS(payload)、STOP_SUBMIT时的fromJS(fieldErrors)会反复调用这些结构方法如果 store 里是 Immutable 状态却用 plain reducer就会在对象与 Immutable 集合之间混用 API导致状态更新异常。切换方式很简单仓库根目录的 immutable.js 入口直接指向require(./lib/immutable)对应 src/immutable/reducer.js它用 immutable structure 调用createReducer// 方式一ES6 命名导入 import { reducer as formReducer } from redux-form/immutable // 方式二ES5 require var formReducer require(redux-form/immutable).reducer // 之后同样挂到 form 下 const reducers { form: formReducer } const reducer combineReducers(reducers) const store createStore(reducer)注意Immutable 模式下普通 selector如getFormValues读取的是Map/List展示到组件前通常需要toJS()转换对应 immutable structure 的toJS方法当前仓库中immutable是可选 peer 依赖见 package.json 的peerDependenciesMeta只有在使用 Immutable.js 管理全局状态时才需要安装。五、reducer 的底层结构state 切片长什么样虽然Reducer.md没有列出状态结构但结合 src/createReducer.js 中各个行为处理器可以完整还原一个表单状态切片form state slice的形状{ registeredFields: {}, // 已注册字段key 为字段名值为 { name, type, count } values: {}, // 当前字段值 initial: {}, // 初始值initialValues fields: {}, // 字段元信息如 { myField: { touched, visited, active, autofilled } } active: myField, // 当前聚焦字段可选 anyTouched: true, // 是否有任意字段被触碰过可选 submitting: true, // 提交中可选 submitFailed: true, // 提交失败可选 submitSucceeded: true, // 提交成功可选 submitErrors: {}, // 提交错误可选 asyncErrors: {}, // 异步校验错误可选 syncErrors: {}, // 同步校验错误可选 syncWarnings: {}, // 同步警告可选 error: 表单级错误, // 表单级错误可选 warning: 表单级警告, // 表单级警告可选 triggerSubmit: true // 触发提交标记可选 }其中registeredFields比较特殊它以registeredFields[fieldName]这种扁平结构存储字段注册信息且维护count计数REGISTER_FIELD时count 1UNREGISTER_FIELD时count - 1用于处理同一名称字段被多个Field同时注册/卸载的场景见 src/createReducer.js 与UNREGISTER_FIELD处理。action 路由与行为表createReducer内部维护了一张behaviors行为表键为 src/actionTypes.js 中定义的全部 30 余种 action 类型值是对应的状态更新函数。例如CHANGE写入/删除values[field]可选清除asyncErrors、submitErrors除非persistentSubmitErrors为 true并记录touchedFOCUS/BLUR维护active、fields[field].active、visitedSTOP_SUBMIT/STOP_ASYNC_VALIDATION解析_error与字段级错误分别写入error、submitErrors、asyncErrorsINITIALIZE/RESET重建values与initialINITIALIZE还支持keepDirty、keepValues、keepSubmitSucceeded、updateUnregisteredFields等精细控制数组系列ARRAY_*push/pop/insert/remove/move/swap 等通过arraySplice同时同步values、fields、syncErrors、syncWarnings、submitErrors、asyncErrors六组数据保证数组操作后所有派生状态一致。所有 action 类型都以redux-form/为前缀见 src/actionTypes.js且必须携带meta.form指明目标表单。外层byForm通过isReduxFormAction判断前缀、再按meta.form切分状态最终由decorate包装后导出。快速验证仓库 src/tests/helpers/reducer.*.js 目录下为每种 action 准备了独立的测试桩而 src/tests/reducer.spec.js 汇总了全部 reducer 行为测试。运行npm testjest见 package.json 的scripts.test即可验证上述行为。六、进阶reducer.plugin()注入自定义逻辑当默认的 action 行为无法满足业务时redux-form 提供了reducer.plugin(ObjectString, Function)来扩展 reducer详见 docs/api/ReducerPlugin.md。Returns a form reducer that will also pass each action through additional reducers specified. The parameter should be an object mapping fromformNameto a(state, action) nextStatereducer.Thestatepassed to each reducer will only be the slice that pertains to that form.Flux 架构的美妙之处在于所有 reducer 都会收到所有 action因此你的表单可以响应「其他模块派发的 action」。典型场景登录表单在AUTH_LOGIN_FAIL时清空密码字段。完整示例继承自官方文档import { createStore, combineReducers } from redux import { reducer as formReducer } from redux-form import { AUTH_LOGIN_FAIL } from ../actions/actionTypes const reducers { // ... your other reducers here ... form: formReducer.plugin({ login: (state, action) { // ----- login 是传给 reduxForm() 的表单名 switch (action.type) { case AUTH_LOGIN_FAIL: return { ...state, values: { ...state.values, password: undefined // ----- 清空密码值 }, registeredFields: { ...state.registeredFields, password: undefined // ----- 同时清掉字段状态touched 等 } } default: return state } } }) } const reducer combineReducers(reducers) const store createStore(reducer)plugin 的底层实现源码级解读decorate中的plugin()实现位于 src/createReducer.js核心逻辑为先让 action 走完 redux-form 原生 reducerthis(state, action)得到处理后的状态若 action 携带meta.form且未开启receiveAllFormActions只把该 action 交给对应表单的 plugincallPlugin(processed, form)若 action 不针对任何表单则把 action广播给所有 pluginObject.keys(reducers).reduce(callPlugin, processed)每次调用 plugin 时传入三参该表单的当前切片状态、action、以及原生 reducer 处理前的完整状态第三个参数getIn(state, key)可用于对比前后差异。plugin 配置项默认行为plugin 只接收「非表单专属」的 action 以及「自己表单」的 action不会收到其他表单的 actionreceiveAllFormActions: true如果希望 plugin 也收到其他表单的 action可这样配置form: formReducer.plugin( { login: (state, action) { /* ... */ } }, { receiveAllFormActions: true } )使用注意事项官方文档特别提醒这是高级操作会直接修改 redux-form 状态切片的内部结构操作不慎可能破坏表单状态。编写 plugin 时务必保持不可变更新如示例中的展开运算符并确保default分支原样返回state。reducer.plugin的测试覆盖见 src/tests/helpers/reducer.plugin.js 与 src/tests/reducer.spec.js。七、常见问题与排查指引问题原因与解决表单不渲染任何字段状态未挂载 reducer 或挂载 key 不是form确认combineReducers({ form: formReducer })必须挂到其他位置使用getFormState: state state.yourSlice.form告诉reduxForm()状态切片位置Immutable.js 下状态异常必须改用redux-form/immutable的 reducer见第四节plain 与 immutable 的structure不可混用plugin 收不到其他表单的 action默认只分发非表单 action 与本表单 action需要跨表单监听时加{ receiveAllFormActions: true }AUTH_LOGIN_FAIL后密码没清空检查 plugin 的 key 是否与reduxForm({ form: login })的表单名完全一致见 docs/api/ReducerPlugin.md 示例八、关联资源本文核心依据docs/api/Reducer.md、docs/api/ReducerPlugin.md核心实现src/reducer.js、src/createReducer.js、src/actionTypes.js、src/actions.js结构适配层src/structure/plain/index.js、src/structure/immutable/index.js、src/immutable/reducer.js入口与导出src/index.js、immutable.js测试佐证src/tests/reducer.spec.js、src/tests/helpers 目录下的reducer.*.js测试桩、src/tests/reduxForm.spec.js其他相关 APIdocs/api/ReduxForm.mdgetFormState等配置项、docs/api/Selectors.md、docs/api/ActionCreators.md【免费下载链接】redux-formA Higher Order Component using react-redux to keep form state in a Redux store项目地址: https://gitcode.com/gh_mirrors/re/redux-form创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考