react实践- - 封装一个简易表单form组件

903 阅读1分钟

一.表单实现功能:

Form 组件可以被 ref 获取。然后可以调用实例方法 submitForm 获取表单内容,用于提交表单,resetForm 方法用于重置表单。

Form组件自动过滤掉除了FormItem之外的其他React元素

FormItem 中 name 属性作为表单提交时候的 key ,还有展示的 label

④ FormItem 可以自动收集 <Input/> 表单的值。

二.form编写

类组件中,父组件可以通过ref获取子组件的实例,但在函数组件中,父组件没办法通过ref直接拿到子组件的方法和状态,因此需要对ref进行转发:

  • forwardRef、useImperativeHandle实现的子组件方法转发。

为了辨别form传入组件的身份

  • 给函数组件或者类组件绑定静态属性来证明它的身份。

利用isValidElement判断元素类别,进行form表单的元素进行过滤

  • React.isValidElement验证元素类别。

利用React.Children.map方式进行react元素遍历,React.Children.clone对元素进行隐式注入 props

  • React.Children.map 、 React.Children.clone,实现了可控render。
import React, { useState, forwardRef, useImperativeHandle, useRef } from 'react'

// /* Input 组件, 负责回传value值 */
function Input({ onChange, value }) {
    return <input className="input"
        onChange={(e) => (onChange && onChange(e.target.value))}
        value={value}
    />
}
// /* 给Component 增加标签 */
Input.__COMPONENT_TYPE = 'input'

function FormItem(props) {
    const { children, name, handleChange, value = '', label } = props
    const onChange = (value) => {
        /* 通知上一次value 已经改变 */
        handleChange(name, value)
    }
    return <div className="form" >
        <span className="label" >{label}:</span>
        {
            React.isValidElement(children) && children.type.__COMPONENT_TYPE === 'input'
                ? React.cloneElement(children, { onChange, value })
                : null
        }
    </div>

}

FormItem.__COMPONENT_TYPE = 'formItem'

const Form = forwardRef((props, ref) => {

    const [formData, setFormData] = useState({})
    // 提交表单
    const submitForm = (callback) => {
        callback(formData)
    }
    // 重置表单
    const resetForm = () => {
        const resetVale = {}
        Object.keys(formData).forEach(item => {
            resetVale[item] = ''
        })
        setFormData(resetVale)
    };
    // 设置表单数据层
    const setValue = (name, value) => {
        setFormData({
            ...formData,
            [name]: value
        })

    };
    // 将子组件中的方法暴露给
    useImperativeHandle(ref, () => ({
            submitForm,
            resetForm,
            setValue
        
    }))
    return React.Children.map(props?.children, (child) => {
        if (child.type.__COMPONENT_TYPE === 'formItem') {
            const { name } = child.props
            const Children = React.cloneElement(child, {
                key: name,
                handleChange: setValue,
                value: formData[name]
            })
            return Children
        }
    })
})
Form.__COMPONENT_TYPE = 'form'


export default () => {
    const form = useRef(null)
    const submit = () => {
        /* 表单提交 */
        form.current.submitForm((formValue) => {
            console.log(formValue)
        })
    }
    const reset = () => {
        /* 表单重置 */
        form.current.resetForm()
    }
    return <div className="box" >
        <Form ref={form}>
            <FormItem label="我是"
                name="name"
            >
                <Input />
            </FormItem>
            <FormItem label="我想对大家说"
                name="mes"
            >
                <Input />
            </FormItem>
            <input placeholder="不需要的input" />
            <Input />
        </Form>
        <div className="btns" >
            <button className="searchbtn"
                onClick={submit}
            >提交</button>
            <button className="concellbtn"
                onClick={reset}
            >重置</button>
        </div>
    </div>
}