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