在React上重用表单

你好

我们在BCS中有一个管理面板和许多表格,但是在React社区中,没有普遍接受的方法-如何设计它们以供重用。 Facebook官方指南没有有关在实际情况下(需要验证和重复使用)如何使用表格的详细信息。 有人使用redux-form,formik,final-form甚至编写自己的解决方案。


在本文中,我们将展示在React上使用表单的一种选择。 我们的堆栈将像这样:React + formik + Typescript。 我们将展示:

  • 组件应该做什么。
  • 在道具级别的配置,字段和验证。
  • 如何使表格可重用。
  • 优化渲染器。
  • 比我们的方法不方便。

通过新的业务任务,我们了解到我们将需要制作15-20个相似的表格,并且假设可能会有更多的表格。 我们在配置中有一个恐龙表单,该表单处理来自“ store”中的数据,通过“ sagas”发送了保存和执行请求的动作。 她很出色,具有商业价值。 但是它已经是不可扩展的和不可重用的,只有差的代码和附加的拐杖。

任务是:重写表单,以便可以无限次地重复使用它。 好吧,请记住函数式编程,它具有纯函数,它们不使用外部数据(在我们的例子中为redux),仅使用参数(props)中发送的内容。

就是这样。

我们组件的想法是,您创建一个包装器(容器),并在其中编写与外界合作的逻辑(从Redux存储接收数据并发送操作)。 为此,容器组件必须能够通过回调接收一些信息。 表单道具的完整列表:

interface IFormProps { //          IsSubmitting?: boolean; //     submitText?: string; //    resetText?: string; //       (  ) validateOnChange?: boolean; //     blur'e  (  ) validateOnBlur?: boolean; // ,      . config: IFieldsFormMetaModel[]; //  . fields: FormFields; //    validationSchema: Yup.MidexSchema; //     onSubmit?: () => void; //     reset  onReset?: (e: React.MouseEvent<HTMLElement>) => void; //    onChangeField?: ( e: React.SyntaticEvent<HTMLInputElement, name: string; value: string ) => void; //      +    onChangeFields?: (values: FormFields, prop: { isValid }) => void; } 

使用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; //          prop 'fields' /**    checked */ checked?: boolean; /** enum,      */ type?: ElementTypes; /**    */ label?: string; /**    */ helperText?: string; /**      */ required?: boolean; /**      */ disabled?: boolean; /**  -    */ minLength?: number; /**            */ initialValue?: IInitialValue; /**      */ selectItems?: ISelectItems[]; //   select, dropdown   }>; } 

基于此接口,我们创建一个对象数组,并根据此方案渲染“ 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,以避免不必要的渲染器。

结论


我们的表单并不理想,对于每种情况,我们都必须创建一个容器包装来处理数据。 将它们保存在“商店”中,进行转换并发出请求。 有重复的代码要删除。 我们正在尝试寻找一种新的解决方案,在该解决方案中,根据道具的不同,表单将从商店中获取所需的键,包括字段,操作,图表和配置。 在以下帖子之一中,我们将告诉您其中的原因。

Source: https://habr.com/ru/post/zh-CN461919/


All Articles