React Form表单的简单实现

3,569 阅读4分钟

先来捋一捋表单的实现思路吧!

  1. 阅读antd这部分源码之后,发现antd3和antd4在表单的实现思路还是有很大的差异的。
  • antd3的设计思路是需要实现表单数据的收集、校验、提交等特性。通过使用高阶组件进行扩展。高阶组件给表单组件传递一个input组件的包装函数用于接管input组件的输入事件,并统一管理表单数据。关于校验功能的实现是高阶组件给表单传递一个校验函数使其具备数据检验的功能。
  • antd4是基于rc-field-form实现的.在数据管理方面与antd3有很大的不同 ! antd3将需要管理的数据存在form的state中,这样会带来很大的渲染消耗,当某一个子组件的状态发生了变化,form表单下所有的组件都会被重新渲染。 antd4则将数据存放在一个store中进行统一管理,某一组价状态的更新不会造成全部组件的重新渲染
  1. 基于rc-field-form实现Form表单的准备工作
  • 执行 yarn add rc-filed-form;
  • 了解rc-field-form的基本使用
  • 自己实现my-field-form
  1. 目录结构
  • input.js
  • index.js --入口文件
  • Form.js
  • Field.js
  • useForm.js
  • context.js --通过Context来跨层级传递数据
  1. rc-field-form 基本使用
  • class 组件基本使用方式
import React from 'react';
// import Form, { Field } from 'rc-field-form';
import Form, { Field } from '../../components/my-field-form';
import Input from '../../../components/input';
import './index.less';

// 定义校验规则
const nameRules = {required: true, message: "请输入姓名!"};
const passworRules = {required: true, message: "请输入密码!"};

class RcFormUse extends Component{
    formRef = React.createRef();
    componentDidMount() {
        console.log("form", this.formRef.current); 
        // this.formRef.current.setFieldsValue({username: "default"});
    }
    onFinish = val => {
        console.log("onFinish", val); 
    };
    // 表单校验失败执行
    onFinishFailed = val => {
        console.log("onFinishFailed", val);
    };
    render () {
        return (
            <div>
                <h3>RcFormUse</h3>
                <Form
                ref={this.formRef}
                onFinish={this.onFinish} 
                onFinishFailed={this.onFinishFailed}
                >
                    <Field name="username" rules={nameRules}>
                        <Input placeholder="Username" />
                    </Field>
                    <Field name="password" rules={passworRules}>
                        <Input placeholder="Password" />
                    </Field>
                    <button>提交</button>
                </Form>
            </div>
        )
    }
}

export default RcFormUse;
  • 函数式组件基本使用
import React from 'react';
// import Form, { Field } from 'rc-field-form';
import Form, { Field } from '../../components/my-field-form';
import Input from '../../../components/input';
import './index.less';

// 定义校验规则
const nameRules = {required: true, message: "请输入姓名!"};
const passworRules = {required: true, message: "请输入密码!"};

const RcFormUse = (props) => {
  // useForm是自定义组件
  const [form] = Form.useForm();
  const onFinish = val => {
      console.log("onFinish", val);
   };
   // 表单校验失败执行
   const onFinishFailed = val => {
      console.log("onFinishFailed", val);
   };
   useEffect(() => {
   	  // 设置默认值
      form.setFieldsValue({username: "default"});
   }, []);
   return ( 
        <div>
           <h3>RcFormUse</h3>
           <Form
           ref={this.formRef}
           onFinish={this.onFinish} 
           onFinishFailed={this.onFinishFailed}
           >
               <Field name="username" rules={nameRules}>
                   <Input placeholder="请输入用户名" />
               </Field>
               <Field name="password" rules={passworRules}>
                   <Input placeholder="请输入密码" />
               </Field>
               <button>提交</button>
           </Form>
       </div> 
     )
}

export default RcFormUse;
  1. Input 组件封装
import React from 'react';

const Input = (props) => {
	return <input {...props}></input>
}

const CustomInput = (props) => {
	const {value, otherProps} = props;
    return <Input style={{outline: 'none'} value={value} {...otherProps}></Input>
}

export default CustomInput;
  1. index.js 入口文件
import React from 'react';
import _Form from './Form';
import Field from './Filed';
import useForm from './useForm';

// 使用forwardRef转发Form组件中form的ref,实现From表单的class组件使用方式
const Form = React.forwardRef(_Form);
// useForm自定义组件,实现Form表单的函数式组件的使用方式
Form.useForm = useForm;

export {Field};
export default Form;
  1. Context.js
import React from 'react';

// 创建一个Context对象,跨层级穿递数据
const FieldContex = React.creacteContext;

export default FieldContext;
  1. useForm.js
import React from 'react';

/**
 * 存储form数据的仓库,form需要收集数据 
 *
 */
class FormStore {
    constructor() {
        this.store = {}
        // 存放Feild组件,用于强制更新
        this.fieldEntities = {};  // 源码中使用的是数组
        // 存放组件传递的方法
        this.callbacks = {}
    }

    // 合并callbacks
    setCallbacks = (callback) => {
        this.callbacks = {
            ...this.callbacks,
            ...callback,
        }
    }

    // 注册组件,用于组件更新
    registerEntity = entity => {
    	/** entity => {props: {...}, getCntrolled: {...}, context: {...}, refs: {...}, state: null, unregisterEntity: {...}, updater: {...}, _reactInternalInstance: {...}, _reactInternals: {...}} **/
        console.log('注册的filed组件:', entity);
        this.fieldEntities = {
            ...this.fieldEntities,
            [entity.props.name]: entity
        }

        // 取消组件注册
        return () => {
            delete this.fieldEntities[entity.props.name];
        }
    }
    getFieldVal = (name) => {
        const v = this.store[name];
        return v;   
    }
    setFieldVal = (newStore) => {
        this.store ={
            ...this.store,
            ...newStore
        }

        // 更新field组件,执行强制更新
        Object.keys(newStore).forEach(name => {
            this.fieldEntities[name].onStoreChange();
        })
    }
    // 表单校验
    validate = () => {
        let err = [];
        // 遍历 this.fieldEntities
        Object.keys(this.fieldEntities).forEach(key => {
            const entity = this.fieldEntities[key];
            const { rules } = entity.props;
            // 获取name对应的值
            const value = this.getFieldVal(key);
            // 获取校验规则,这里值校验了一条规则
            const rule = rules && rules[0];
            // console.log('key:', key);
            // console.log('entity:', entity);
            // console.log('rule:', rule);
            // console.log('value:',value);
            if(rule && rule.required && value === undefined){
                err.push({
                    [key]: rule.message,
                    value,
                });
            }
        })

        return err;
    }
    
    // 响应表单的提交事件
    submit = () => {
        const { onFinish, onFinishFailed } = this.callbacks;
        const err = this.validate();
        if(err.length === 0) {
            onFinish && onFinish({...this.store})
        } else {
            console.log('failed:', err);
            onFinishFailed && onFinishFailed(err, {...this.store})
        }
    }
    getForm() {
        return {
            getFieldVal: this.getFieldVal,
            setFieldVal: this.setFieldVal,
            registerEntity: this.registerEntity,
            submit: this.submit,
            setCallbacks: this.setCallbacks,
        }
    }
}

// 自定义hook
const useForm = () => {
    const formRef = React.useRef();
    if(!formRef.current) {
        const formStore = new FormStore();
        formRef.current = formStore.getForm();
    }
    return [formRef.current];
}


export default useForm;
  1. Form.js
import React, { useImperativeHandle } from 'react';
import useForm from './useForm';
import FieldContext from './FiledContext';

const Form = ({ children, onFinish, onFinishFailed }, ref) => {
    
    const [formInstance] = useForm();
    console.log('formInstance:', formInstance);
    // 通过使用useImperativeHandle配合forwardRef转发组件ref式, 可以自定义我们需要暴露给父组件的实例值
    // 将formInstance暴露给父组件
    useImperativeHandle(ref, () => formInstance);
    // 接收Form组件传入的方法,存储到useForm.js中的callbacks中
    formInstance.setCallbacks({
        onFinish,
        onFinishFailed
    })
    const onSubmit = (e) => {
        e.preventDefault();
        e.stopProstopPropagation();
        formInstance.submit();
    }
    return (
        <form onClick={onSubmit}>
            <FieldContext.Provider value={formInstance}>
                {children}
            </FieldContext.Provider>
        </form>
    )
}

export default Form;
  1. Field.js
import React from 'react';
import FieldContext from './FiledContext';

class Field extends React.Component{
    static contextType = FieldContext;
    componentDidMount () {
        // 在context中添加注册当前对象
        this.unregisterEntity = this.context.registerEntity(this);
    }
    componentWillUnmount () {
        // 取消订阅
        if(this.unregisterEntity) {
            this.unregisterEntity();
        }
    }
    // 执行强制更新
    onStoreChange() {
        this.forceUpdate();
    }
    getCntrolled = () => {
        const { getFieldVal, setFieldVal } = this.context;
        const { name } = this.props;
        return {
            value: getFieldVal(name),
            onChange: (e) => {
                const newVal = e.target.value
                setFieldVal({[ name ]: newVal});
                console.log('值:', newVal);
            }
        }
    }
    render(){
        const { children } = this.props;
        return React.cloneElement(children, this.getCntrolled())
    }
}

export default Field;