封装Form表单组件极简版

确定要实现的功能

import React, { useRef } from 'react'
import Form from './Form'
import FormItem from './FormItem'
import Input from './Input'

const FormContext = () => {
  const formRef = useRef(null)

  // 功能1:提交时获取所有表单数据
  const submit = () => {
    formRef.current.submitForm((formData) => {
      console.log(formData)
    })
  }

  // 功能2:重置表单数据
  const reset = () => {
    formRef.current.resetForm()
  }

  return (
    <div className="form-context">
      <Form ref={formRef}>
        <FormItem name="name" label="用户名">
          <Input />
        </FormItem>
        <FormItem name="password" label="密码">
          <Input type="password" />
        </FormItem>
      </Form>
      <div className="operate">
        <button onClick={submit}>提交</button>
        <button onClick={reset}>重置</button>
      </div>
    </div>
  )
}

export default FormContext
  1. 功能1:点击提交按钮时,获取所有表单数据
  2. 功能2:点击重置按钮时,重置表单数据

这里为 Form 组件绑定了ref,是为了调用 Form 组件内部的方法,目前 Form 组件对外暴露的方法有:

  1. submitForm
  2. resetForm

Form组件实现

这里有四个关注点

  1. 函数组件传入 ref 需要使用 forwardRef 承接
  2. 函数组件对外暴露API需要使用 useImperativeHandle 进行处理
  3. Form 组件只处理 FormItem 子元素,其他元素自动忽略
  4. 基于 FormContext 下发表单数据源以及数据源修改方法
import React, { createContext, forwardRef, useImperativeHandle, useState } from 'react'

// 基于 FormContext 下发表单数据源以及修改方法
export const FormContext = createContext({})

const Form = forwardRef((props, formRef) => {
  // 统一管理表单数据源
  const [formData, setFormData] = useState({})

  // 对外暴露的API
  useImperativeHandle(formRef, () => ({
    // 表单提交
    submitForm: (callback) => {
      callback && callback({ ...formData })
    },
    // 表单重置
    resetForm: () => {
      let data = { ...formData }
      Object.keys(data).forEach((item) => {
        data[item] = ''
      })
      setFormData(data)
    },
  }))

  // Form表单内的表单项修改统一的赋值方法
  const handleChange = (name, value) => {
    setFormData({
      ...formData,
      [name]: value,
    })
  }

  const renderContent = () => {
    const renderChildren = []
    React.Children.map(props.children, (child) => {
      // child.type 子元素自身,检查其静态属性 displayName 是否满足条件
      if (child.type.displayName === 'formItem') {
        renderChildren.push(child)
      }
    })
    return renderChildren
  }
  // 传入数据源以及数据源的修改方法,子孙后代都可读取 value 中的值
  return <FormContext.Provider value={{ formData, handleChange }}>{renderContent()}</FormContext.Provider>
})

Form.displayName = 'form'

export default Form

FormItem 组件实现

这里有三个关注点

  1. 必传参数 nameForm 中的数据源存在对应关系
  2. FormItem 中只处理了 Input 的子元素,其他元素自动忽略
  3. 复写 FormContext.Provider ,为其增加 name 参数的传递,用于标识更新表单数据源中哪一项
import React, { useContext } from 'react'
import { FormContext } from '../Form'

const FormItem = (props) => {
  const context = useContext(FormContext)
  const { children, name, label } = props

  const renderContent = () => {
    // 子元素检查
    if (React.isValidElement(children) && children.type.displayName === 'input') {
      return (
        <div className="form-item">
          <span className="form-item-label">{label}</span>
          {children}
        </div>
      )
    }
    return null
  }

  // 复写 FormContext.Provider,增加 name 参数的传递
  return <FormContext.Provider value={{ ...context, name }}>{renderContent()}</FormContext.Provider>
}

FormItem.displayName = 'formItem'

export default FormItem

Input 组件实现

这里有两个注意点

  1. 获取 FormContext.Provider 提供提供的 value
  2. 根据 name 属性,更新 Form 中的数据源
import React, { useContext } from 'react'
import { FormContext } from '../Form'

const Input = ({ type = 'text' }) => {
  // 获取 `FormContext.Provider` 提供提供的 `value` 值
  const context = useContext(FormContext)

  const handleChange = (e) => {
    // 根据 name 属性,更新 Form 中的数据源
    context.handleChange(context.name, e.target.value)
  }

  return <input type={type} value={context.formData[context.name]} onChange={handleChange} />
}

Input.displayName = 'input'

export default Input

至此一个极简版本的 Form 表单组件便完成

涉及知识点补充

  1. React Context API
  2. React.isValidElement
  3. React.Children.map / React.Children.forEach / React.Children.toArray
  4. React.cloneElement
  5. React.createElement
  6. React Hooks
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值