Form
一、已完成功能
- 验证
- 提交
- 重置
- 设置属性
二、目录结构
三、代码解读
// form.tsx部分代码
const InternalForm = React.forwardRef<unknown, FormProps>((props, ref) => {
// 使用name组成的对象
let store:any = {}
const {
name,
layout='horizontal',
children,
onFinish,
labelCol,
wrapperCol,
component:Component='form',
initialValues
} = props;
const [prefixCls, setPrefixCls] = useGetPrefixCls();
const [refs, setRefs] = useRefs<any>();
const [model, setModel] = useSetState(store)
......
<FormContext.Provider value={{
prefixCls,
labelCol,
wrapperCol,
model,
setModel,
initialValues
}}>
<Component
id={name}
className={classes}
onSubmit={(event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
event.stopPropagation();
onSubmit()
}}
>{
React.Children.map(children,(child, index)=>{
// 具有name属性的字段保存下来,方便验证
const { name, valuePropName} = (child as any)?.props
name && (store[name] = initialValues?.[name]?initialValues[name]:valuePropName==='checked'?false:'')
if (!React.isValidElement(child)) return null;
return React.cloneElement(child,{
ref:setRefs(index)
})
})
}</Component>
</FormContext.Provider>
form的主要操作是获取子组件的方法(通过传入的ref),例如验证和重置,因为有三层的嵌套,传参使用context的方式
// form-item-input.tsx代码
import * as React from 'react'
import AsyncValidator from 'async-validator';
import c from 'classnames'
import { Col } from '../index'
import { ColProps } from '../grid/col';
import { FormContext } from './context'
import { isObject } from '@xt-ui/utils';
export interface FormItemInputRef {
validate:Function
}
export interface FormItemInputProps {
colon?: boolean;
valuePropName?:string;
label?: React.ReactNode;
wrapperCol?: ColProps;
rules?: any;
[propName: string]: any;
}
const FormItemInput = React.forwardRef<unknown, FormItemInputProps>((props, ref) => {
const { children, wrapperCol, rules, valuePropName, name } = props
const formContext = React.useContext(FormContext)
let {
model,
setModel,
prefixCls,
initialValues
} = formContext
const [error, setError] = React.useState('')
// 传给父级验证方法
React.useImperativeHandle(ref,()=>{
return {
props,
validate,
resetField
}
})
// !如果自己设置了col着覆盖父级的col
const mergedWrapperCol: ColProps = wrapperCol || formContext.wrapperCol || {};
// 验证
const validate = (trigger: string, cb?: Function): boolean | void => {
// 获取对应rules
const rules = getFilteredRule(trigger);
if (!rules || rules.length === 0) {
if (cb instanceof Function) {
cb();
}
return true;
}
const descriptor = {[name]: rules };
// 存放rule以字典形式
const validator = new AsyncValidator(descriptor);
const model = { [name]: fieldValue() };
validator.validate(model, { firstFields: true }, (errors) => {
setError(errors ? errors[0].message! : '')
if (cb instanceof Function) {
cb(errors);
}
});
}
const resetField = () => {
// 清除验证提示
setError('')
if(Object.keys(model).length>0){
// !重置如果有initialValues则显示initialValues,否则置空,input的value不能为null或者undefined
model[name] = initialValues?.[name]?initialValues[name]:valuePropName==='checked'?false:''
setModel(model)
}
}
// 获取rules下的value
const fieldValue = () => {
if (!model || !name) { return; }
return rules && model[name]
}
/**
* @description: 查找对应的rules
* @param {string} trigger 事件类型
* @return {*}
* @author: wxy
*/
const getFilteredRule = (trigger: string): Array<any> => {
const rulesList = [].concat(rules);
return rulesList.filter((rule:any) => {
if (!rule?.trigger || trigger === '') return true;
if (Array.isArray(rule.trigger)) {
return rule.trigger.indexOf(trigger) > -1;
} else {
return rule.trigger === trigger;
}
}).map((rule:any) => Object.assign({}, rule));
}
/**
* @description: 通过绑定事件获取实时的值
* @param {React} e 事件对象或者选中状态
* @author: wxy
*/
const getControlField = (e:React.ChangeEvent<any>) => {
let value = e?.target?.value
// console.log(isObject(e));
if(name){
// !onChange返回的值不一样,需要判断
const fieldValue = isObject(e) ? value : e
model[name] = fieldValue
validate('change');
setModel(model)
// 给子组件添加onChange事件会和本身的onChange起冲突
React.Children.map(children,(child:any) => {
child.props?.onChange?.(fieldValue)
})
}
}
return (
<Col {...mergedWrapperCol} className={c([`${prefixCls}-item-control`],{
[`${prefixCls}-item-has-error`]: error !== ''
})}>
<div
className={c([`${prefixCls}-item-control-content`])}>
{
/* 为表单元素添加交互事件 */
React.Children.map(children,child => {
return React.cloneElement(child as any,{
onChange:getControlField,
value:model[name]
})
})
}
</div>
{error && <div className={c([`${prefixCls}-item-error`])}>{error}</div>}
</Col>
)
})
export default FormItemInput
这里会给每一个表单组件都绑定一个onChange和value(核心概念),或者对应的属性就行操作,有一个model属性,通过父组件传过来,onfinsh也是返回这个model,这个model不能自己定义,要不然会有bug,需要使用useState定义
四、验证功能
验证功能使用的是async-validator
插件
import AsyncValidator from 'async-validator';
// 验证
const validate = (trigger: string, cb?: Function): boolean | void => {
// 获取对应rules
const rules = getFilteredRule(trigger);
if (!rules || rules.length === 0) {
if (cb instanceof Function) {
cb();
}
return true;
}
const descriptor = {[name]: rules };
// 存放rule以字典形式
const validator = new AsyncValidator(descriptor);
const model = { [name]: fieldValue() };
validator.validate(model, { firstFields: true }, (errors) => {
setError(errors ? errors[0].message! : '')
if (cb instanceof Function) {
cb(errors);
}
});
}