ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

redux-form FieldArray 完全指南:在 Redux Store 中渲染与操作动态字段数组

redux-form FieldArray 完全指南:在 Redux Store 中渲染与操作动态字段数组 redux-form FieldArray 完全指南在 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-formFieldArray是 redux-form 中用于渲染动态字段数组的核心高阶组件它把数组形态的表单数据如团队成员列表、发票明细行、标签集合接入 Redux 表单状态。本文以 docs/api/FieldArray.md 为主体结合仓库源码深入讲解它的全部 Props、实例 API、注入给包裹组件的fields/meta对象以及迭代字段时的完整写法帮助你在表单中实现任意增、删、插、移、换位的数组编辑能力。一、FieldArray 是什么与 Field 的对比FieldArray的工作方式与Field非常相似使用Field时你传入一个name指向 Redux state 中字段的位置和一个component负责渲染字段component会拿到一组用于将字段连接到 Redux state 的 props使用FieldArray时你同样传入一个name但传给component的将是一组用于查询、更新、迭代整个字段数组的 props——即fields与meta两个对象。简言之Field管单个字段FieldArray管一组字段并且它天然支持数组级的增删改排等操作。二、引入方式ES5CommonJSvar FieldArray require(redux-form).FieldArray // ES5ES6模块导入import { FieldArray } from redux-form // ES6在仓库中FieldArray的默认导出位于 src/FieldArray.js它通过createFieldArray(plain)基于 plain原生 JS 对象数据结构创建同时仓库还提供了基于 ImmutableJS 的版本 src/immutable/FieldArray.js供在 Immutable 结构下使用的场景切换。三、可传给 FieldArray 的 Propsname : String必填字符串路径使用点号与方括号表示法对应表单 values 中的某个值。既可以是简单的firstName也可以是复杂的contact.billing.address[2].phones[1].areaCode。从源码看name在 src/createFieldArray.js 中被PropTypes.string.isRequired强制校验。此外当FieldArray嵌套在FormSection中时实际注册与使用的名字会带上FormSection前缀见 prefixName这一点在 src/tests/FieldArray.spec.js 中有对应测试FormSection namefooFieldArray namebar ...//FormSection时fields.name为foo.bar。component : Component | Function必填一个 React 组件或无状态函数用于渲染字段数组。它会收到fields与meta两组 props详见下文第四节。validateComponentProp见 src/util/validateComponentProp.js负责校验该 prop如果传入非组件对象会在渲染时报Element type is invalid错误测试见 FieldArray.spec.js。validate : (value, allValues, props) error可选字段级校验函数。回调参数依次为当前数组值、表单全部其他值、传给表单的 props。数组合法时返回undefined非法时返回错误通常但不一定是String。需要特别注意的是数组自身的校验错误必须通过_error键返回。源码中 src/ConnectedFieldArray.js 明确注释For an array, the error canONLYbe under_error并通过plain.getIn(syncErrors,${name}._error)读取而 src/createFieldArray.js 的wrapError会把validate的返回值自动包装成{ _error: result }。同时validate也支持传入一个函数数组propTypes 中为PropTypes.oneOfType([PropTypes.func, PropTypes.arrayOf(PropTypes.func)])wrapError会逐个执行、返回第一个非空结果。warn : (value, allValues, props) warning可选字段级警告函数参数与validate完全一致需要警告时返回警告通常但不一定是String不需要时返回undefined。数组级警告同样以_warning键返回源码读取路径为getIn(syncWarnings,${name}._warning)见 ConnectedFieldArray.js。forwardRef : boolean可选为true时可以通过getRenderedComponent()拿到渲染出的组件实例默认false。注意如果component是无状态函数组件则不能使用该 prop无状态函数没有实例可拿。createFieldArray.js 中getRenderedComponent()会先断言forwardRef存在否则抛出错误If you want to access getRenderedComponent(), you must specify a forwardRef prop to FieldArray。props : object可选自定义 props 对象会透传给component并与FieldArray自身提供的 props 合并源码中通过{...fields/meta, ...props, ...rest}的展开顺序实现见 createFieldArrayProps.js。rerenderOnEveryChange : boolean可选为true时FieldArray内的Field每次变化都会触发整个数组组件重渲染。注意这在大列表场景下会显著降低性能官方文档明确提示。默认值为false见 ConnectedFieldArray.js 的defaultProps其性能逻辑在shouldComponentUpdate中体现只有数组长度变化、元素顺序变化、或在rerenderOnEveryChange开启时元素内容deepEqual不相等才会触发更新见 ConnectedFieldArray.js。四、实例 APIInstance APIFieldArray组件实例上可用以下属性与方法API说明name : String嵌套在FormSection中时返回带FormSection前缀的name否则返回传入的namevalid : boolean该字段数组通过校验时为true否则为falsegetRenderedComponent()返回渲染出的组件实例前提是提供了forwardRef且组件不是无状态函数组件此外从 createFieldArray.js 的 getter 实现还可以看到实例上另有dirty、pristine、value属性它们均代理自内部ref.current即ConnectedFieldArray实例。五、传递给包裹组件的 PropsFieldArray通过component渲染出的组件会收到 redux-form 注入的 props这些 props 被划分为fields与meta两个对象你自己额外传给FieldArray的 props 则位于 props 对象的根层级与fields、meta平级类型定义见 src/FieldArrayProps.types.js.flow。5.1 Fields Propsfields伪数组fields是一个伪数组pseudo-array具备与 JavaScript 原生Array相似的读写能力。其具体构造见 src/createFieldArrayProps.js各成员如下成员签名说明fields.nameFunction返回name嵌套在FormSection时返回带前缀的名称fields.forEach(callback) void遍历数组每个值详见迭代一节fields.get(index) value获取数组指定下标的值fields.getAll() Array获取数组全部值使用 ImmutableJS 时返回 ImmutableListfields.insert(index, value) void在任意下标插入新值fields.lengthNumber数组当前长度fields.map(callback) Array遍历数组并返回每次回调结果的数组详见迭代一节fields.move(from, to) void将元素从下标from移到下标tofields.pop() value移除数组末尾元素并返回该元素fields.push(value) void向数组末尾追加值fields.remove(index) void移除任意下标的元素fields.removeAll() void移除数组全部元素fields.shift() value移除数组首个元素并返回该元素fields.splice(index, removeNum, value) void对数组执行原生Array.splice语义的操作fields.swap(indexA, indexB) void交换两个下标的元素fields.unshift(value) void向数组开头追加值除此之外源码还提供了文档未强调的fields.reduce(callback, initial)与fields.some(callback)实现方式与map/forEach同构便于需要归约或存在性判断的场景直接使用。关于不是 mutator的重要说明move、pop、push、remove、removeAll、shift、splice、swap、unshift、insert这些方法并不会直接修改本地数组——它们会 dispatch 一个 Redux action 更新 store 中的表单状态从而触发组件重渲染。这正是 redux-form 保持表单状态全部存于 Redux store这一设计原则的体现。从源码层面看这些方法在 ConnectedFieldArray.js 中通过bindActionCreators绑定了arrayInsert、arrayMove、arrayPop、arrayPush、arrayRemove、arrayRemoveAll、arrayShift、arraySplice、arraySwap、arrayUnshift十个 action creator均绑定当前的nameaction 类型常量定义于 src/actionTypes.js形如redux-form/ARRAY_PUSH等而 reducer 侧的数组处理逻辑集中在 src/createReducer.js例如ARRAY_PUSH在数组末尾 splice 插入 payload、ARRAY_REMOVE删除指定下标、ARRAY_SWAP同时交换values/fields/submitErrors/asyncErrors四个根键下对应位置的值、ARRAY_MOVE先删除再插入。pop()与shift()的返回值则是从当前value中读取后返回的见 createFieldArrayProps.js。5.2 Meta Propsmeta元数据meta下是 redux-form 为字段数组跟踪的状态元数据构造见 createFieldArrayProps.js成员类型说明meta.dirtyboolean数组中任一字段相对初始化值发生变化即为true与pristine相反meta.errorString可选数组未通过校验时的错误同步、异步、提交校验错误都会汇聚到这里数组级错误来自校验函数返回的_error键meta.formString传给reduxForm()的form配置名meta.invalidboolean数组校验失败存在校验错误时为true与valid相反meta.pristineboolean数组全部字段与初始化值一致时为true与dirty相反meta.submitFailedboolean表单因任何原因提交失败时为truemeta.submittingboolean表单正在提交时为truemeta.validboolean数组通过校验无校验错误时为true与invalid相反meta.warningString可选数组未通过警告校验时的警告数组级警告来自校验函数返回的_warning键从 createFieldArrayProps.js 的实现可以看到meta.error的优先级顺序为syncError || asyncError || submitError即同步错误 异步错误 提交错误meta.invalid/meta.valid直接由!!error/!error推导。meta类型定义中还包括touched字段见 FieldArrayProps.types.js.flow。这些meta值在 ConnectedFieldArray.js 的connect映射中被计算例如pristine由deepEqual(value, initial)得到数组相关错误分别从syncErrors/asyncErrors/submitErrors下的name._error路径读取。六、迭代Iterationmap / forEach 的回调参数使用fields.forEach()或fields.map()遍历数组时回调会收到以下三个参数参数类型说明nameString渲染数组中该字段时传给Field的name。例如FieldArray的name为foo.bar且数组有 3 项时回调依次收到foo.bar[0]、foo.bar[1]、foo.bar[2]indexNumber元素在数组中的下标fieldsObject对fieldsprop 的引用便于直接调用swap、remove、pop等方法无需闭包作用域这一约定在 createFieldArrayProps.js 中实现回调的第一个参数由${fieldName}[${index}]模板拼接而成。测试用例 FieldArray.spec.js 也验证了forEach/map会以[foo[0], 0, fields]、[foo[1], 1, fields]、[foo[2], 2, fields]的形式依次调用回调。以下是一个完整的团队成员动态列表示例取自文档可直接运行const renderSubFields (member, index, fields) ( li key{index} button typebutton titleRemove Member onClick{() fields.remove(index)} / h4Member #{index 1}/h4 Field name{${member}.firstName} typetext component{renderField} labelFirst Name / Field name{${member}.lastName} typetext component{renderField} labelLast Name / /li ) const renderMembers ({ fields }) ( ul button typebutton onClick{() fields.push({})} Add Member /button {fields.map(renderSubFields)} /ul )要点解析fields.push({})在点击Add Member时为数组追加一个空对象随后fields.map触发重渲染并展开新行每行的Remove Member按钮调用fields.remove(index)而index正是回调的第二个参数每个子字段通过模板字符串${member}.firstName生成形如members[0].firstName的完整路径再交给Field渲染——这正是数组内字段必须用Field单独渲染的核心用法。与Field的完整对比可参考 docs/api/Field.md嵌套分组场景可参考 docs/api/FormSection.md。七、源码级原理FieldArray 如何工作7.1 生命周期与字段注册createFieldArray.js 中的FieldArray类在componentDidMount时通过this.props._reduxForm.register(name, FieldArray, ...)注册自己type为FieldArray注册时把validate/warn分别包装为输出_error/_warning键的校验器componentDidUpdate中若name含FormSection前缀后的名字发生变化会先unregister旧名再注册新名componentWillUnmount时注销。因此FieldArray必须位于reduxForm()装饰的组件内部否则构造时会抛出 FieldArray must be inside a component decorated with reduxForm()createFieldArray.js对应测试 FieldArray.spec.js。7.2 渲染链路与 props 构造FieldArray.render()渲染的是ConnectedFieldArray由connect连接 store后者在render()中剔除component、forwardRef、validate、warn、rerenderOnEveryChange、_reduxForm等内部 props 后调用createFieldArrayProps构造{ fields, meta, ...自定义props }并createElement(component, props)见 ConnectedFieldArray.js。测试 FieldArray.spec.js 验证了这些内部 API props不会被泄漏给自定义组件。7.3 性能shouldComponentUpdate 与 rerenderOnEveryChangeConnectedFieldArray.js 的shouldComponentUpdate是性能关键默认情况下只有当数组长度变化、元素顺序变化或任意 props 深度不等时才重渲染rerenderOnEveryChange开启后才会逐个元素做deepEqual比较让数组中某个Field的变化立即刷新整个数组。这也解释了文档中大列表慎开rerenderOnEveryChange的性能警告。7.4 数组错误模型_error/_warning数组是结构化的容器其整体错误与元素错误必须区分元素级错误由数组内每个Field的validate独立产生并挂在该元素路径上数组级错误则统一存放在${name}._error下warning 同理为${name}._warning。因此在使用meta.error/meta.warning时只需在校验函数中返回{ _error: ... }这样的对象结构即可源码的读取与包装逻辑已在 ConnectedFieldArray.js 与 createFieldArray.js 中自动完成。八、使用建议与注意事项数组级校验一定用_error键validate返回{ _error: 错误信息 }时错误才会出现在meta.error中直接返回字符串不会生效。所有增删改排方法都是 action 驱动的不要试图直接修改fields内部数据统一使用push/remove/insert/move/swap/splice/shift/unshift/pop/removeAll它们会通过 Redux 更新表单状态。key建议使用 index 或稳定 ID文档示例用key{index}若数组支持频繁的move/swap/insert从渲染稳定性考虑可为每行数据引入稳定 ID 作 key。forwardRef不能配无状态函数组件需要getRenderedComponent()时component必须是 class 组件。大列表谨慎开启rerenderOnEveryChange默认值false已经过shouldComponentUpdate优化非必要不打开。name支持深层路径contact.billing.address[2].phones[1].areaCode这类点号加方括号路径均受支持可配合 docs/api/Field.md 中的同名Field做任意嵌套深度的数组表单。通过将数组的当前值、错误、元数据全部收敛到 Redux storereducer 逻辑集中在 src/createReducer.jsFieldArray让动态列表类表单的开发从手工维护本地数组状态 提交时同步转变为声明式渲染 action 驱动更新这也是它在 redux-form 生态中处理嵌套、动态、可排序数组表单时的核心价值所在。【免费下载链接】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),仅供参考
返回列表