前端UI组件【免费下载链接】redux-formA Higher Order Component using react-redux to keep form state in a Redux store项目地址https://gitcode.com/gh_mirrors/re/redux-form点击查看免费下载导读v6是 redux-form 历史上一次彻底重写completely rewritten的版本其根本原因是一次基础性的设计变更——控制反转Inversion of Control。本指南以官方 MigrationGuide 为骨架结合当前仓库redux-form 8.3.10位于src/的源码实现逐项拆解 v5 → v6 的最小迁移路径包括用Field/FieldArray取代fields数组、用SubmissionError包装提交错误、去除mapStateToProps/mapDispatchToProps透传、normalization 从 reducer 下沉到字段级、以及 form state 内部结构的重排。读完本文你将掌握把 v5 表单代码升级到 v6 的完整实操清单并理解新架构背后的性能动机与源码依据。为什么没有增量升级路径v6 的 API 变更波及内部状态结构与连接模型官方明确说明不存在从 v5 平滑升级的过渡路径没有 deprecation 警告的中间态只能一次性迁移。但FieldAPI 的设计初衷就是最小化迁移痛苦本文即围绕“最短迁移距离”展开。1. 控制反转为什么 v6 必须重写1.1 v5 的性能瓶颈在 v5 中只有最外层表单组件连接connect到 Redux store每个字段的 propsvalue、onChange、onBlur等由表单组件统一生成并向下传递。这导致一个严重问题整个表单组件必须在每一次改变表单值的按键上完整重渲染re-render。对小型登录表单这不成问题但当一个表单拥有几十甚至上百个字段时每次按键都会引发整棵组件树的重渲染性能极其糟糕。1.2 v6 的解决方案在 v6 中每一个字段都单独连接到 Redux store。外层表单组件虽然也连接 store但其连接方式保证了它不必在每次值变化时刷新。值变化只触发对应Field及其子树的更新这正是“控制反转”的含义字段自身掌控自己的状态与渲染时机而非依赖父表单统一分发。从源码看v6 的Field由 createField.js 创建内部通过createConnectedField见 src/createField.js#L18生成一个ConnectedField并执行真正的 connect。而Field本身还承担了字段生命周期管理挂载时向_reduxForm.register()注册src/createField.js#L32-L39卸载时注销src/createField.js#L67-L69并通过shallowCompare做shouldComponentUpdate优化src/createField.js#L41-L43。这套“字段自我注册 局部更新”的机制正是 v6 高性能表单的根基。从仓库现状看v8 版本中reduxForm装饰器位于 src/reduxForm.js底层为 src/createReduxForm.js它仍负责提供handleSubmit等表单级能力但字段渲染与状态订阅已完全交给独立的Field。2. 告别fields拥抱Fieldv5 要求你在reduxForm()配置中提供fields名称数组然后装饰后的组件会收到一个fields对象 prop。而“把字段名字符串转换成输入框所需的 propsvalue、onChange、onBlur等”的机制在 v6 中就是新的Field组件。2.1 v5 写法import React, { Component } from react import { reduxForm } from redux-form class MyForm extends React.Component { render() { const { fields: { username, password }, handleSubmit } this.props return ( form onSubmit{handleSubmit} div label htmlForusernameUsername/label div input typetext {...username} / {username.touched username.error ( span classNameerror{username.error}/span )} /div /div div label htmlForpasswordPassword/label div input typepassword {...password} / {/* 与上面重复的代码 */} {password.touched password.error span classNameerror{password.error}/span} /div /div button typesubmitSubmit/button /form ) } } export default reduxForm({ form: myForm, fields: [username, password] // v5必须声明字段数组 })(MyForm)2.2 v6 写法import React, { Component } from react import { reduxForm, Field } from redux-form // 新增 Field 导入 const renderInput field // 定义无状态组件渲染输入框与错误 div input {...field.input} type{field.type}/ // type 由下方 Field 指定 {field.meta.touched field.meta.error span classNameerror{field.meta.error}/span} /div class MyForm extends React.Component { render() { const { handleSubmit } this.props // 不再有 fields prop return ( form onSubmit{handleSubmit} div label htmlForusernameUsername/label Field nameusername // 指定字段名 component{renderInput} // 复用同一个渲染组件 typetext/ // type 传给 renderInput /div div label htmlForpasswordPassword/label Field namepassword component{renderInput} // 同一渲染组件复用 typepassword/ /div button typesubmitSubmit/button /form ) } } export default reduxForm({ form: myForm // 不再需要 fields 数组 })(MyForm)2.3 核心变化解读输入/输出方式的逆转v5 中字段名字符串集中作为输入、字段对象集中作为输出由redux-form一次性批量转换v6 中“字段名 → 字段对象”的转换在每个字段所在位置逐个完成一次一个。field.input与field.meta拆分v6 中传给渲染组件的字段对象被组织为field.input含value、onChange、onBlur、onFocus等输入 props与field.meta含touched、error、warning、pristine、dirty等元信息。这从源码中ConnectedField的 props 类型定义 FieldProps.types.js.flow 与 createFieldProps.js 可以得到印证。组件复用v6 中同一个renderInput渲染函数可被多个Field复用上面两个字段共用消除了 v5 里每个字段都要复制一遍的错误展示代码。3.handleSubmit与onSubmit拥抱SubmissionError关于表单提交唯一的变化是你的提交验证错误必须包装在SubmissionError对象中以便将验证错误与AJAX/服务器错误区分开。3.1 v5 写法MyForm onSubmit{values ajax .send(values) // 以任何方式把数据发给服务器... .catch(error { // 如何把服务端验证错误传回来由你决定 if (error.validationErrors) { return Promise.reject(error.validationErrors) } else { // 其他通信错误如何处理也由你决定 reportServerError(error) } }) } /3.2 v6 写法MyForm onSubmit{values ajax.send(values).catch(error { if (error.validationErrors) { throw new SubmissionError(error.validationErrors) // ----- 唯一区别 } else { reportServerError(error) } }) } /3.3SubmissionError的源码实现SubmissionError定义在 src/SubmissionError.js其内部机制如下// flow import ExtendableError from es6-error const __FLAG__ redux-form/submission-error-flag export class SubmissionError extends ExtendableError { /** private */ static __FLAG__ __FLAG__ errors: Object constructor(errors: Object) { super(Submit Validation Failed) this.errors errors } } export function isSubmissionError(error: any): boolean { return (error error.constructor error.constructor.__FLAG__ __FLAG__) true }关键点构造函数接收一个errors对象形如{ fieldName: error message }消息固定为Submit Validation Failed。它通过静态标志redux-form/submission-error-flag与普通错误区分isSubmissionError()帮助函数据此判断抛出的错误是否为提交验证错误。在提交执行链路 src/handleSubmit.js 中executeSubmit捕获到错误后通过isSubmissionError(submitError) ? submitError.errors : undefined提取字段错误src/handleSubmit.js#L29随后触发stopSubmit(error)与setSubmitFailed(...fields)把错误写入表单状态的submitErrors同时设置submitFailed标记——这正是服务端验证错误能自动回填到对应字段的底层机制。4.mapStateToProps与mapDispatchToProps自己用connect()在 v5 中reduxForm()装饰器接受mapStateToProps和mapDispatchToProps参数并原样透传给react-redux的connect()。v6 不再这样做。如果你需要访问 Redux store 中的其他值或把 action creator 绑定到dispatch必须自己用connect()额外装饰你的表单组件import { connect } from react-redux import { reduxForm } from redux-form const MyForm ({ handleSubmit, otherStoreValue }) ( form onSubmit{handleSubmit} {/* 字段 */} /form ) export default connect( state ({ otherStoreValue: state.something.else }) // 自己处理 mapStateToProps )(reduxForm({ form: myForm })(MyForm))从源码看v6 的reduxForm返回的装饰器只关心表单相关配置form、validate、initialValues等与表单内部 actionchange、blur、submit等见 src/createReduxForm.js#L39-L67 中arrayActions与 formActions 的拆分。对外部 store 数据的访问一律交由用户自行组合connect()职责边界更清晰。5. 同步验证Sync Validation同步验证与 v5 完全相同只有一个小差异如果你使用 ImmutableJS传给同步验证函数的values将是一个Immutable.Map但返回的 errors始终是普通 JS 对象和以前一样。const validate values { // values 在 ImmutableJS 下是 Immutable.Map const errors {} if (!values.username) { errors.username Required } if (!values.password) { errors.password Required } return errors // 总是普通 JS 对象 } export default reduxForm({ form: myForm, validate })(MyForm)仓库中同步错误与警告的计算逻辑可在 src/createReduxForm.js调用generateValidator生成验证器与 src/reducer.js 底层的UPDATE_SYNC_ERRORS/UPDATE_SYNC_WARNINGS行为src/createReducer.js#L724-L763中看到验证结果会写入syncErrors/syncWarnings分支并同步到error/warning顶层字段。6. 用initialValues初始化初始化方面没有实质变化v6 只是修复了一批恼人的 bug官方文档提及的问题单如 #514、#621、#628、#756。修复后的关键保证是在 v6 中每个字段在首次渲染时就会带上它的初始值initial value。export default reduxForm({ form: myForm, initialValues: { username: defaultUser, password: secret } })(MyForm)从 reducer 的INITIALIZE行为src/createReducer.js#L389-L510可以看到 v6 对初始化的完整支持除了基础赋值还实现了keepDirty保留脏字段的用户编辑仅覆盖 pristine 字段、keepValues、updateUnregisteredFields等精细控制并持久化已注册字段registeredFields与旧的 syncErrors/syncWarnings 以便重算。7. 异步验证Async Validation没有变化工作方式与 v5 完全一样。只需要像 v5 一样提供asyncValidate函数const asyncValidate (values, dispatch, props, blurredField) { return fetch(/api/check-username, { method: POST, body: JSON.stringify(values) }) .then(res { if (res.status 409) { throw { username: That username is taken } // 抛出的字段错误对象 } }) } export default reduxForm({ form: myForm, asyncValidate, asyncBlurFields: [username] // 触发异步验证的失焦字段 })(MyForm)在 src/handleSubmit.js 的提交链路中异步验证结果asyncErrors会与同步错误通过mergeErrors合并src/handleSubmit.js#L7-L10然后决定是否放行executeSubmit。8. 深层字段Deep Fieldsv6 的深层字段没有任何神秘之处——直接在字段名上使用点语法dot-syntax即可。8.1 v5 写法render() { const { fields: { contact: { shipping: { street } } } } this.props return ( div input typetext {...street}/ /div ) }8.2 v6 写法render() { return ( div Field namecontact.shipping.street componentinput typetext/ /div ) }深层字段的值读写由structure/plain与structure/immutable两套结构实现支撑见 src/structure/plain/setIn.js 与 src/structure/immutable/setIn.js点路径会按层级写入/读取嵌套对象。同时Field在注册时会通过prefixNamesrc/util/prefixName.js处理名称前缀如FormSection场景保证深层路径在复杂布局下依然准确。9. 字段数组Field Arraysv5 中字段数组对象是作为整个表单的 prop 传入的v6 中你需要使用FieldArray组件就像使用Field一样。9.1 v5 写法render() { const { fields: { awards } } this.props; return ( div ul {awards.map((award, index) li key{index} label htmlForawardAward #{index 1}/label input typetext {...award.input}/ /li)} /ul button onClick{() awards.addField()}Add Award/button /div ) }9.2 v6 写法const renderAwards ({ fields }) div ul {fields.map((name, index) li key{index} label htmlFor{name}Award #{index 1}/label Field name{name} typetext componentinput/ /li)} /ul button onClick{() fields.push()}Add Award/button /div render() { return ( div FieldArray nameawards component{renderAwards}/ /div ) }9.3FieldArray的字段 APIv6 的FieldArray渲染函数会收到一个fields对象其完整能力定义在 src/createFieldArrayProps.js 中包括成员说明fields.map(cb)/fields.forEach(cb)遍历数组回调收到(name, index, fields)name形如awards[0]fields.push(value)在末尾追加元素对应arrayPushfields.pop()/fields.shift()弹出末尾/头部元素并返回被移除值fields.insert(index, value)在指定索引插入fields.remove(index)移除指定索引元素fields.removeAll()清空数组fields.move(from, to)移动元素fields.swap(indexA, indexB)交换元素fields.splice(index, removeNum, value)通用切片操作fields.unshift(value)头部插入fields.get(index)/fields.getAll()读取值fields.length/fields.name数组长度与字段名meta含dirty、pristine、error、invalid、valid等元信息注意方法名的变化v5 的awards.addField()在 v6 中对应fields.push()awards.removeField(index)对应fields.remove(index)。这些数组操作在 reducer 层由ARRAY_INSERT、ARRAY_PUSH、ARRAY_REMOVE、ARRAY_MOVE、ARRAY_SWAP等行为处理见 src/createReducer.js#L142-L258且arraySplice会同步维护values、fields、syncErrors、syncWarnings、submitErrors、asyncErrors六个分支的一致性src/createReducer.js#L108-L140。FieldArray的入口在 src/FieldArray.js底层 src/createFieldArray.js。10. Normalization从 reducer 下沉到字段级v6 中normalization 从 reducer 层移动到了字段级别。10.1 v5 写法reducer 层const upper value value value.toUpperCase() const reducer combineReducers({ // other reducers form: form.normalize({ myForm: { myUppercaseField: upper } }) })10.2 v6 写法字段级const upper value value value.toUpperCase() // ... Field namemyUppercaseField componentinput normalize{upper}/10.3 normalize 的源码签名Field的normalize函数签名从 src/createField.js#L96-L105 可读出为normalize(value, previousValue, nextValues, previousValues, name)即当前新值、之前的值、包含新值的完整下一组表单值、当前表单值集合、字段名。例如实现“仅允许数字”的过滤式 normalizeconst onlyNumbers (value, previousValue) value !/^[0-9]$/.test(value) ? previousValue : value Field namephone componentinput normalize{onlyNumbers}/normalize 的完整调用链与“格式化/解析”生命周期可进一步参考仓库文档 docs/ValueLifecycle.md其中format、parse、normalize三者的先后顺序与各自职责有系统说明。11. 监听其他 actionplugin()API 与状态结构变化plugin()API与 v5 完全相同但由于 form state 的内部结构发生了改变你原来修改它的 plugin reducer需要更新。11.1 v5 的字段级扁平结构{ myField: { value: myValue, initial: myInitialValue, asyncError: myAsyncError, submitError: mySubmitError, touched: true, visited: true } }11.2 v6 的分类聚合结构{ values: { myField: myValue }, initial: { myField: myInitialValue }, asyncErrors: { myField: myAsyncError }, submitErrors: { myField: mySubmitError }, fields: { myField: { touched: true, visited: true } } }11.3 结构变化的本质v5 把“值、初始值、各类错误、交互状态”平铺在单个字段对象里v6 则按数据类别纵向聚合values当前值、initial初始值、asyncErrors/submitErrors/syncErrors错误、fieldstouched、visited、active、autofilled等交互状态。这不仅让 plugin reducer 需要改写也对应了createReducer中rootKeys [values, fields, submitErrors, asyncErrors]的设计src/createReducer.js#L108。plugin()的实现位于 src/createReducer.js#L793-L825它包裹原 reducer先让 redux-form 处理完 action再把仅针对该表单或所有表单的action 交给用户提供的 plugin reducer并以“返回值是否变化”来决定是否写回 state。一个适配 v6 结构的 plugin 示例import { reducer as formReducer } from redux-form const myPlugin (state, action) { if (action.type SOME_ACTION) { return { ...state, values: { ...state.values, myField: action.payload } } } return state } const reducer combineReducers({ form: formReducer.plugin({ myForm: myPlugin }) })提示plugin reducer 的三个参数依次为(previousState, action, formState)——第三个参数formState是 action 派发前的整份表单状态便于你读取初始值等上下文见 src/createReducer.js#L798-L808。12. 已知问题Known Issuesreact-hot-loader如果你正在使用react-hot-loader 1.X并看到错误Uncaught TypeError: Cannot read property wrapped of undefined那么你需要升级 react-hot-loader 到 3.X。react-hot-loader v3 的接入方式与传统 HMR 略有不同官方建议在应用入口处做如下处理经典用法import React from react import { AppContainer } from react-hot-loader import { render } from react-dom import MyApp from ./MyApp const renderApp Component { render( AppContainer Component/ /AppContainer, document.getElementById(root) ) } renderApp(MyApp) if (module.hot) { module.hot.accept(./MyApp, () renderApp(MyApp)) }13. 迁移检查清单对照以下清单完成 v5 → v6 的逐项改造迁移点v5v6字段声明reduxForm({ fields: [...] })fieldsprop在 JSX 中逐个使用Field name... /字段渲染手写value/onChange/onBlur展开与错误展示渲染组件从field.input与field.meta取值提交验证错误Promise.reject(validationErrors)throw new SubmissionError(validationErrors)连接 storereduxForm({ mapStateToProps, mapDispatchToProps })自行用connect()二次装饰深层字段解构嵌套fields.contact.shipping.streetField namecontact.shipping.street /字段数组fields.awardsaddField()/removeField()FieldArray nameawards /fields.push()/fields.remove()Normalizationform.normalize({ formName: { field: fn } })Field normalize{fn} /plugin reducer读写扁平字段对象读写values/initial/*Errors/fields分类结构同步/异步验证函数签名不变完全一致Immutable 下values为Immutable.Maperrors 仍为普通对象14. 延伸阅读仓库内资源官方 API 文档Field、FieldArray、reduxForm、SubmissionError、Reducer值生命周期详解docs/ValueLifecycle.mdformat/parse/normalize的协作顺序入门指南docs/GettingStarted.md核心源码字段实现 src/createField.js 与 src/ConnectedField.js、数组实现 src/createFieldArray.js 与 src/createFieldArrayProps.js、提交链路 src/handleSubmit.js、状态结构 src/createReducer.js测试佐证字段与数组的行为测试见 src/tests/Field.spec.js、src/tests/FieldArray.spec.js、src/tests/reducer.spec.js赞分享前端UI组件【免费下载链接】redux-formA Higher Order Component using react-redux to keep form state in a Redux store项目地址https://gitcode.com/gh_mirrors/re/redux-form点击查看免费下载相关推荐ant-design Form 组件 DOM 结构与状态类迁移指南ant design Form 组件 DOM 结构与状态类迁移指南 本文基于 ant design 仓库 components/form/changelog.m前端UI组件设计系统redux-form Field 组件完全指南将每个输入控件接入 Redux Storeredux form Field 组件完全指南将每个输入控件接入 Redux Store Field 是 redux form 中用于连接单个输入控件与 Re前端UI组件Buzz 教程十分钟完成第一次本地语音转录Buzz 教程十分钟完成第一次本地语音转录 Buzz 是一款在你个人电脑上离线完成语音转录和翻译的工具底层引擎是 OpenAI 的开源语音识别模型 Whis人工智能语音音频本地部署桌面应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
