Antd4 Form 实现思路及思考

使用范式及设计思路

代码地址: https://github.com/kizuner-bonely/oh-my-mini-react

首先我们需要一个表单,它的使用范式如下所示:

import { createRef, Component, useEffect } from 'react'
import Form, { Field } from '@form'
import { Input } from '@form/components'

const nameRules = { required: true, message: '请输入姓名!' }
const passwordRules = { required: true, message: '请输入密码!' }

export default function MyRCFieldForm() {
  const [form] = Form.useForm()

  const onFinish = (val: any) => {
    console.log('onFinish', val)
  }

  // 表单校验失败执行
  const onFinishFailed = (val: any) => {
    console.log('onFinishFailed', val)
  }

  useEffect(() => {
    console.log('form', form)
    form.setFieldValue({ username: 'default' })
  }, [])

  return (
    <div>
      {/* 函数组件 */}
      <h3>MyRCFieldForm</h3>
      <Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}>
        <Field name="username" rules={[nameRules]}>
          <Input placeholder="input UR Username" />
        </Field>
        <Field name="password" rules={[passwordRules]}>
          <Input placeholder="input UR Password" />
        </Field>
        <button>Submit</button>
      </Form>

      {/* 类组件 */}
      <MyClassFieldForm />
    </div>
  )
}

class MyClassFieldForm extends Component {
  formRef = createRef()

  onFinish = (val: any) => {
    console.log('onFinish', val)
  }

  // 表单校验失败执行
  onFinishFailed = (val: any) => {
    console.log('onFinishFailed', val)
  }

  render() {
    return (
      <div>
        <h3>MyClassFieldForm</h3>
        <Form
          ref={this.formRef}
          onFinish={this.onFinish}
          onFinishFailed={this.onFinishFailed}
        >
          <Field name="username" rules={[nameRules]}>
            <Input placeholder="input UR Username" />
          </Field>
          <Field name="password" rules={[passwordRules]}>
            <Input placeholder="input UR Password" />
          </Field>
          <button>Submit</button>
        </Form>
      </div>
    )
  }
}

通过使用范式我们可以得到以下结论:

  1. 在组件层面,我们需要 <Form><Field>,它们最终渲染的是 <Field> 包裹的表单元素
  2. 对于函数组件,我们需要使用 useForm() 来生成 Form 实例,并将其通过参数传给 <Form>
  3. 对于类组件,我们需要创建一个 ref 来获取 Form 实例
  4. 示例中的 button 没有绑定事件也能正常提交

根据以上结论,我们可以推导出以下设计思路:

  • 根据结论1,无论是 <Form> 还是 <Field>,它们都会装饰并返回 children
  • 根据结论2、3:
    • 类组件和函数组件对于 Form 的控制方式不同:
      • 函数组件先通过 useForm() 生成 Form 实例,并通过参数传给 <Form>,这样函数组件就能通过该 Form 实例对 Form 进行控制。
      • 类组件是先生成一个 ref,通过参数传给 <Form>,这样类组件就能通过 ref 对 Form 进行控制。
    • 对于 <Form>,无论使用它的是类组件还是函数组件都需要有一个 Form 实例,对于函数组件来说 Form 实例是函数组件自己生成并当作参数传给 <Form>
      而对于类组件来说并没有自己生成 Form 实例,因此在 <Form> 内部要对这两种情况作兼容。
      兼容方式很明显,如果使用者有通过参数传 Form 实例就用外界传来的,如果没有则自己生成并将该 Form 实例返回给外界传入的 ref。
    • 整个 Form 是通过 Form 实例进行控制,比如说规则校验、提交等等。
  • 根据结论4,<Form> 的子组件肯定包括 <form>

请添加图片描述
图1:Form 整体架构

1. 最简实现

首先把整体架构搭好,后面再补充细节,为了实现最简版本,一开始只考虑调用组件为函数组件。

从图一可以看出,Form Instance 需要和 <Form> 交互,也要和 <Field> 交互,需要注意的是 <Form><Field> 不一定是父子组件,中间可能隔着 <div> 这样的组件。因此通过参数传递有点不合适,使用 context 能保证它们都能拿到同一 Form Instance

  • Form.tsx
  • Field.tsx
  • useForm.ts
  • FieldContext.ts
  • form.d.ts

form.d.ts

import {
    FormStore } from './useForm'
  
export type FormProps = {
   
  form?: ReturnType<FormStore['getForm']>
  children: JSX.Element | JSX.Element[]
} & FormCallbacks

export type FieldProps = {
   
  name: string
  children: ReactElement<any, string | JSXElementConstructor<any>>
  rules?: Array<{
    required: boolean, message: string }>
}

export type FieldContextType = ReturnType<FormStore['getForm']>

FieldContext.ts

import type {
    FieldContextType } from './form.d'
import {
    createContext } from 'react'
import {
    FormStore } from './useForm'

// 这里的参数只是为了不让 ts 报错,真正的 Form 实例还是要在 <Form> 中获得
export const FieldContext = createContext<FieldContextType>(new FormStore().getForm())

useForm.ts

import {
    useRef } from 'react'

export default function useForm() {
   
  const store = useRef(new FormStore()).current
  return [store]
}

// Form 实例仓库,要实现两个功能点
// 1.存储状态
// 2.更新状态 -> 更新仓库值 + 通知组件更新
export class FormStore {
   
  // store 存的是一个个键值对,键名是 <Field> 的 name,键值是 <Field> 包裹表单的值
  private store: Record<string, any> = {
   }
  
  // 取
  getFieldsValue = () => {
   
    return {
    ...this.store }
  }
  
  getFieldValue = (name: string) => {
   
    return this.store[name]
  }
  
  // 存
  setFieldValue = (val: Record<string, any>) => {
   
    // 1.更新仓库值
    this.store = {
    ...this.store, ...val }
    // todo 2.通知组件更新
  }
  
  // 统一接口
  getForm = (
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值