你好
我们在BCS中有一个管理面板和许多表格,但是在React社区中,没有普遍接受的方法-如何设计它们以供重用。 Facebook官方指南没有有关在实际情况下(需要验证和重复使用)如何使用表格的详细信息。 有人使用redux-form,formik,final-form甚至编写自己的解决方案。
在本文中,我们将展示在React上使用表单的一种选择。 我们的堆栈将像这样:React + formik + Typescript。 我们将展示:
- 组件应该做什么。
- 在道具级别的配置,字段和验证。
- 如何使表格可重用。
- 优化渲染器。
- 比我们的方法不方便。
通过新的业务任务,我们了解到我们将需要制作15-20个相似的表格,并且假设可能会有更多的表格。 我们在配置中有一个恐龙表单,该表单处理来自“ store”中的数据,通过“ sagas”发送了保存和执行请求的动作。 她很出色,具有商业价值。 但是它已经是不可扩展的和不可重用的,只有差的代码和附加的拐杖。
任务是:重写表单,以便可以无限次地重复使用它。 好吧,请记住函数式编程,它具有纯函数,它们不使用外部数据(在我们的例子中为redux),仅使用参数(props)中发送的内容。
就是这样。
我们组件的想法是,您创建一个包装器(容器),并在其中编写与外界合作的逻辑(从Redux存储接收数据并发送操作)。 为此,容器组件必须能够通过回调接收一些信息。 表单道具的完整列表:
interface IFormProps {
使用Formik
我们使用<Formik />组件。
render() { const { fields, validationSchema, validateOnBlur = true, validateOnChange = true, } = this.props; return ( <Formik initialValues={fields} render={this.renderForm} onSubmit={this.handleSubmitForm} validationSchema={validationSchema} validateOnBlur={validateOnBlur} validateOnChange={validateOnChange} validate={this.validateFormLevel} /> ); }
在验证表单的属性中,我们调用this.validateFormLevel方法,在此方法中,我们使容器组件有机会获得所有更改的字段并检查它们是否有效。
private validateFormLevel = (values: FormFields) => { const { onChangeFields, validationSchema } = this.props; if (onChangeFields) { validationSchema .validate(values) .then(() => { onChangeFields(values, { isValid: true }); }) .catch(() => { onChangeFields(values, { isValid: false }); }); } }
在这里,我们必须再次调用验证以便使容器清楚字段是否有效。 提交表单时,我们只需调用prop`onSubmit`:
private handleSubmitForm = (): void => { const { onSubmit } = this.props; if (onSubmit) { onSubmit(); } }
对于道具1-5,一切都应该清楚。 让我们继续到“ config”,“ fields”和“ validationSchema”。
道具'配置'
interface IFieldsFormMetaModel { sectionName?: string; sectionDescription?: string; fieldsForm?: Array<{ name?: string;
基于此接口,我们创建一个对象数组,并根据此方案渲染“ section”->“ section field”。 因此,如果您需要标题和注释,我们可以一次显示该部分的多个字段,也可以一次显示几个字段。 渲染如何工作,我们稍后再展示。
配置的简短示例:
export const config: IFieldsFormMetaModel[] = [ { sectionName: ' ', fieldsForm: [{ name: 'subject', label: '', type: ElementTypes.Text, }], }, { sectionName: '', sectionDescription: ' ', fieldsForm: [{ name: 'reminder', disabled: true, label: '', type: ElementTypes.CheckBox, checked: true, }], }, ];
根据业务数据,设置“名称”键的值。 道具“ fields”键中使用相同的值来传输甲酸的原始值或更改后的值。
对于上面的示例,“字段”可能看起来像这样:
const fields: SomeBusinessApiFields = { subject: ' ', reminder: 'yes', }
为了进行验证,我们需要传递Yup Schema。 我们将该表单提供给带有容器props的表单,在其中描述与外部数据(例如请求)的交互。
形式不能以任何方式影响方案,例如:
export const CreateClientSchema: ( props: CreateClientProps, ) => Yup.MixedSchema = (props: CreateClientProps) => Yup.object( { subject: Yup.string(), description: Yup.string(), date: dateSchema, address: addressSchema(props), }, );
渲染和场优化
为了进行渲染,我们制作了一张地图,用于按键快速搜索。 它看起来简洁明了,搜索速度比使用switch更快。
fieldsMap: Record< ElementTypes, ( state: FormikFieldState, handlers: FormikHandlersState, field: IFieldsFormInfo, ) => JSX.Element > = { [ElementTypes.Text]: ( state: FormikFieldState, handlers: FormikHandlersState, field: IFieldsFormInfo ) => { const { values, errors, touched } = state; return ( <FormTextField key={field.name} element={field} handleChange={this.handleChangeField(handlers.setFieldValue, field.name)} handleBlur={handlers.handleBlur} value={values[field.name]} error={touched[field.name] && errors[field.name] || ''} /> ); }, [ElementTypes.TextSearch]: (...) => {...}, [ElementTypes.TextArea]: (...) => {...}, [ElementTypes.Date]: (...) => {...}, [ElementTypes.CheckBox]: (...) => {...}, [ElementTypes.RadioButton]: (...) => {...}, [ElementTypes.Select]: (...) => {...}, };
每个组件字段都是有状态的。 它位于一个单独的文件中,并包装在`React.memo`中。 所有值都是通过props传递的,绕过了child,以避免不必要的渲染器。
结论
我们的表单并不理想,对于每种情况,我们都必须创建一个容器包装来处理数据。 将它们保存在“商店”中,进行转换并发出请求。 有重复的代码要删除。 我们正在尝试寻找一种新的解决方案,在该解决方案中,根据道具的不同,表单将从商店中获取所需的键,包括字段,操作,图表和配置。 在以下帖子之一中,我们将告诉您其中的原因。