ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

redux-form FormSection 完全指南:用 name 前缀拆分并复用表单区块

redux-form FormSection 完全指南:用 name 前缀拆分并复用表单区块 前端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点击查看免费下载FormSection是 redux-form 提供的一个轻量级布局组件它通过为内部所有Field、Fields、FieldArray的字段名自动添加统一前缀让开发者可以把大型表单拆解成可跨表单复用的子组件。读完本文你将掌握FormSection的完整 API、底层sectionPrefix前缀机制、嵌套区块的字段命名规则以及订单表单买家/收货人信息复用这类真实场景的落地写法。一、FormSection 是什么在 redux-form 中表单状态始终保存在 Redux store 里字段的路径由字段名决定。当表单越来越大时把地址、联系人这类重复出现的区块抽成独立组件是常见的工程化手段。但直接抽组件有一个问题每个区块的字段名必须手工带上前缀如buyer.address.streetName否则多个区块会在 store 里互相覆盖。FormSection正是为了解决这一问题而设计的。官方文档docs/api/FormSection.md给出的定义是TheFormSectioncomponent makes it easy to split forms into smaller components that are reusable across multiple forms. It does this by prefixing the name ofField,FieldsandFieldArraychildren, at any depth, with the value specified in thenameprop.即它会把所有子级任意深度Field、Fields、FieldArray的名称统一加上name属性指定的前缀。也就是说FormSection自己不渲染任何输入控件它只是通过 React Context 向下传递一个名称前缀让子孙字段在注册、读写 store 时都带上这个前缀。从源码看FormSection的核心实现非常短小src/FormSection.jsclass FormSection extends ComponentPropsWithContext { render() { const { _reduxForm, children, name, component, ...rest } this.props if (React.isValidElement(children)) { return createElement(ReduxFormContext.Provider, { value: { ...this.props._reduxForm, sectionPrefix: prefixName(this.props, name) }, children }) } return createElement(ReduxFormContext.Provider, { value: { ...this.props._reduxForm, sectionPrefix: prefixName(this.props, name) }, children: createElement(component, { ...rest, children }) }) } }二、可传给 FormSection 的 Props1.name : String必填The name all child fields should be prefixed with.所有子字段需要被加上的前缀名称。它是必填项在propTypes中被声明为PropTypes.string.isRequired见 src/FormSection.js。2.component : String | Component可选If you giveFormSectionmore than one child element, it will be forced to create a component to wrap them with. You can specify what type of component you would like it to be (div,section,span). Defaults todiv.当FormSection有多个子元素时必须创建一个包裹组件来容纳它们。你可以指定该包裹组件的类型原生标签字符串div、section、span或任意 React 组件。默认值是div对应源码中的FormSection.defaultProps { component: div }重要细节当FormSection只有一个子元素时它不会包裹多余的div。这一点由测试用例验证src/tests/FormSection.spec.js渲染FormSection namefooField namebar //FormSection后页面中div标签数量为 0。这也是为什么上文源码中存在React.isValidElement(children)分支——单一 React 元素直接放进Provider无需包装。Note that any additional props (e.g. className, style) that you pass toFormSectionwill be passed along to the wrapper component.额外 props 透传你传给FormSection的任何额外 props如className、style都会原样传递给包裹组件。注意name和component两个 props 会被消费掉不会透传下去。测试用例对此有明确断言src/tests/FormSection.spec.jsFormSection namefoo componentsection classNameform-section style{{ fontWeight: bold }} Field namebar componentinput / Field namebaz componentinput / /FormSection断言结果为className form-section、style.fontWeight bold而props.name与props.component均为 falsy不会被透传到 DOM。三、工作原理sectionPrefix 与 ReduxFormContextFormSection能够自动加前缀靠的是 redux-form 内部基于 React Context 的_reduxForm机制src/ReduxFormContext.jsexport const ReduxFormContext React.createContext(null)FormSection的渲染结果本质上是一个ReduxFormContext.Provider它把从reduxForm()高阶组件拿到的_reduxForm复制一份并覆写sectionPrefix字段。前缀的拼接逻辑在工具函数prefixName中src/util/prefixName.jsconst formatName ({ _reduxForm: { sectionPrefix } }, name) sectionPrefix ? ${sectionPrefix}.${name} : name也就是说只要 context 中存在sectionPrefix字段名就会变成前缀.字段名否则原样返回。这个前缀会沿着组件树向下传播由下游组件消费Field注册、取值、派发 change/blur 等动作时都会走prefixName参见 src/createField.js名称变化时还会自动注销旧名、注册新名Fields在 src/ConnectedFields.js 中从_reduxForm解构出sectionPrefix并处理字段名FieldArray在 src/ConnectedFieldArray.js 中把sectionPrefix传入createFieldArrayProps后者会先剥离前缀再拼出fields[i]这类数组路径src/createFieldArrayProps.js。需要留意的是sectionPrefix的初始值是undefined见 src/createReduxForm.js只有被FormSection包住时才会变成具体的字符串前缀因此未被包裹的普通表单完全不受影响。四、完整示例订单表单复用 Party 组件官方文档给出的典型场景是订单表单买家buyer和收货人recipient的信息字段完全相同因此抽成一个Party组件而Party内部又包含一组地址字段地址也值得再抽成一个Address组件。完整代码如下//Address.js class Address extends React.Component { render() { return div Field namestreetName componentinput typetext/ Field namenumber componentinput typetext/ Field namezipCode componentinput typetext/ /div } } //Party.js class Party extends React.Component { render() { return div Field namegivenName componentinput typetext/ Field namemiddleName componentinput typetext/ Field namesurname componentinput typetext/ FormSection nameaddress Address/ /FormSection /div } } //OrderForm.js class OrderForm extends React.Component { render() { return form onsubmit{...} FormSection namebuyer Party/ /FormSection FormSection namerecipient Party/ /FormSection /form } } //dont forget to connect OrderForm with reduxForm()外层表单用reduxForm()装饰例如import { reduxForm } from redux-form OrderForm reduxForm({ form: order })(OrderForm)Address、Party这样的区块组件可以是普通 class 组件或函数组件因为FormSection的前缀能力来自 React Context与组件自身是否连接 Redux 无关。五、最终字段名与 store 结果结构上述示例中字段的完整名称最终会变成buyer.address.streetName这样的点路径对应 Redux store 中的嵌套结构{ buyer: { givenName: xxx, middleName: yyy, surname: zzz, address: { streetName: undefined, number: 123, zipCode: 9090 } }, recipient: { givenName: aaa, middleName: bbb, surname: ccc, address: { streetName: foo, number: 4123, zipCode: 78320 } } }这套字段名即 store 路径的约定贯穿 redux-form 的取值、校验、错误上报等所有环节——getFormValues、getFormSyncErrors等 selector参见 src/selectors 与 docs/api/Selectors.md返回的都是这种嵌套结构因此FormSection拆出的区块与整体表单的 Redux 状态天然一致。六、嵌套 FormSection前缀自动拼接FormSection支持任意深度嵌套前缀会自动逐层拼接。测试用例src/tests/FormSection.spec.js验证了嵌套场景FormSection namedeep FormSection namefoo Field namebar component{input} / /FormSection /FormSection最终字段的input.name为deep.foo.bar并且能从 store 正确读取到deep.foo.bar路径下的值。这与文档示例中Party内嵌Addressbuyer.address.streetName的机制完全相同。七、进阶技巧继承 FormSection 固化默认前缀对于Address这类很少改变区块名的组件官方文档建议直接继承FormSection并设置默认name这样在使用处无需再写FormSection nameaddressclass Address extends FormSection { //ES2015 syntax with babel transform-class-properties static defaultProps { name: address } render() { return ( div Field namestreetName componentinput typetext / Field namenumber componentinput typetext / Field namezipCode componentinput typetext / /div ) } } //Regular syntax: /* Address.defaultProps { name: address } */注意这里使用了static defaultProps需babel-plugin-transform-class-properties支持或等价的Address.defaultProps ...写法。由于defaultProps的优先级低于显式传入的 props调用处仍可用Address nameshippingAddress /覆盖默认前缀实现默认地址、可覆盖的灵活复用。八、使用注意事项与边界行为结合源码与测试src/tests/FormSection.spec.js使用FormSection时有几点需要注意必须位于 reduxForm() 装饰的表单内部。FormSection的构造函数会检查props._reduxForm否则抛出FormSection must be inside a component decorated with reduxForm()src/FormSection.js测试同样断言了该行为src/tests/FormSection.spec.js。component prop 必须是合法组件。它经由validateComponentProp校验src/util/validateComponentProp.js传入普通对象等非法值会在渲染时报Element type is invalid错误src/tests/FormSection.spec.js。单子元素不产生多余包裹层。如上文所述只有一个子元素时FormSection不会包一层div这对输出干净 DOM 很有帮助。对Field、Fields、FieldArray三类组件全部生效且覆盖任意嵌套深度校验、警告、异步错误等字段元数据同样以带前缀的字段名存储测试中对registeredFields的断言如foo.bar[0]即为佐证。Immutable 结构同样支持。测试同时以 plain 对象与 immutable 结构两套实现运行src/tests/FormSection.spec.js配合 src/immutable 目录下的等价实现使用即可。九、小结FormSection通过一句name前缀约定把可复用的表单区块从理想变成了开箱即用的能力对外它是布局组件对内它是sectionPrefix的 Context 提供者。掌握它之后无论是订单表单里的买家/收货人还是大型后台系统中反复出现的地址、联系信息模块都可以安全地抽成独立组件并在任意多个表单中复用而无需手工拼接字段路径。赞分享前端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点击查看免费下载相关推荐TanStack Form 的 Angular 表单组合指南用 TanStackAppField 与 tanstack-with-form 拆分大型表单TanStack Form 的 Angular 表单组合指南用 TanStackAppField 与 tanstack with form 拆分大型表单 导读前端UI组件portless 状态目录架构解析~/.portless 文件布局与 sudo 路由共享指南portless 状态目录架构解析~/.portless 文件布局与 sudo 路由共享指南 portless 状态目录 ~/.portless 是这款本开发工具CLI如何快速配置eslint-config-love从ECMAScript Modules到CommonJS的终极指南如何快速配置eslint config love从ECMAScript Modules到CommonJS的终极指南 eslint config love是一款创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表