antd 的 form 表单简易实现

77 阅读2分钟

Antd Form 简易实现流程

实现了以下功能

  • 受控组件
  • 表单赋值
  • 表单校验

应用了以下模式和技术

  • 单例模式(伪)

  • 发布订阅模式

  • Context

store

store中存储着 表单的全部数据,和对外提供的的方法

export function createFormStore() {
    // 表单信息
    let store = {
        
    }
    // 表单校验规则
    let rules = {
        
    }
    // 表单校验方法
    let  check = {

    }
    const listeners = new Map();
    return {
        getFieldValue(name) {
            return store[name];
        },
        getFieldsValue(){
            return {...store};
        },
        setFieldValue(name, value) {
            store[name] = value;
            listeners.get(name) && listeners.get(name)();
        },
        setFieldsValue(obj) {
            store = {...store, ...obj};
            listeners.forEach((fn) => fn());
        },
        subscribe: (key,fn: () => void) => {
            listeners.set(key, fn);
            return () => {
                listeners.delete(key);
            }
        },
        // 表单项的校验方法
        registerCheck(name, fn) {
            check[name] = fn;
        },
        registerField(name,rule) {
            rules[name] = rule;
        },
        async validateFields() {
            return new Promise((resolve, reject) => {
                let taskPromise:any = [];
                Object.keys(check).forEach((key) => {
                    taskPromise.push(check[key](store[key]));
                })
                 Promise.all(taskPromise).then((res) => {
                    const errors = res.filter(result => result === false);
                    if (errors.length > 0) {
                        reject(errors);
                    } else {
                        resolve(store);  
                    }
                })
            })
           
        }
    }
}

useForm

单例模式结合useRef,保证重复渲染时,始终拿到同一个store

export function useForm(externalForm?: any): [any] {
    const form = useRef()
    if(!form.current){
        form.current = externalForm || createFormStore();
    }
    return [form.current];
}

FormContext

提供一个context

import React from "react"
const  formContext = React.createContext(null)

export default formContext

Form

初始化 formInstance 或者赋值为 外部传入的 form值,实际上即使执行两次,也是同一个store。算不算单例模式?

同时将formInstance 导出给useImperativeHandle 达到 用户自己使用ref 和 useForm 一个效果

import React,{ forwardRef, useImperativeHandle} from 'react';
import FormContext from './FormContext';
import FormItem from './FormItem';
import {useForm} from './FormStore';
const Form:any = forwardRef((props:any,ref) => {
    const { children ,form } = props;

    const [formInstance] = useForm(form);

    useImperativeHandle(ref, () => ({
        ...formInstance
    })) 

    return (
        <FormContext.Provider value={formInstance}>
            <form>{children}</form>
        </FormContext.Provider>
    )
}) 
Form.Item = FormItem;
Form.useForm = useForm;
export default Form;

FormItem

import React,{useContext,useState,useEffect,useCallback} from 'react'
import FormContext from './FormContext';

const FormItem = ({name,children,rules}) => {
    const form:any = useContext(FormContext);
    // 强制更新
    const [, forceUpdate] = useState({});
    const [error, setError] = useState('');

    useEffect(() => {
        // 将所有的rule规则挂到store中(拓展用)
        form.registerField(name, rules);
        // 订阅 validate 事件
        form.registerCheck(name,validate)
    },[name,rules])

    useEffect(() => {
        // 订阅更新事件
        return form.subscribe(name, () => {
            forceUpdate({}); 
        });
    }, []);

	// 用户输入时校验,以及表单整体校验
    const validate = useCallback((value) => {
        setError('');
        return new Promise((resolve) => {
            if (rules && rules.length) {
                for (let rule of rules) {
                    if (rule.required && !value) {
                        setError(rule.message || '该字段为必填项');
                        resolve(false);
                        return
                    }
                }
                resolve(true);
            }else {
                resolve(true);
            }
        })
        
    },[rules])

    const value  = form.getFieldValue(name) || ''
    const onChange = (e) => {
        form.setFieldValue(name, e.target.value);
        validate(e.target.value);
    };
    return (
        <div className='form-item'>
            {
                //  注意,这里是 受控表单的精髓!!!
                React.cloneElement(children,{
                    value,
                    onChange
                })
            }
            {error && <div style={{ color: 'red' }}>{error}</div>}
        </div>
    )
}
export default FormItem

demo

import React from "react"
import Form from "./Form"
import { useEffect } from "react"

const Demo = () =>{
    const [form] = Form.useForm();
    useEffect(()=>{
        form.setFieldsValue({username:'admin1', password:'root1'})
    },[])

    const test = ()=>{
        form.validateFields().then(res=>{
            console.log(res)
        },err=>{
            console.log(err)
        })
    }
    return (
        <div>
            <Form initialValues={{ username: 'admin', password: 'root' }}  form={form}>
            <Form.Item name="username" rules={[{ required: true, message: 'Please input your username!' }]} >
                <input type="text" placeholder="Username" />
            </Form.Item>
            <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]}>
                <input type="text" placeholder="Username" />
            </Form.Item>
        </Form>
        <button onClick={test}>112</button>
        </div>
        
         
    )
}
export default Demo