表单组件Form的基本目标
- 字段的统一收集和校验
- 适配各种组件的接入
高阶组件的form实现
在没有hooks的那个时期,react表单基本以高阶组件的形式实现。选用高阶组件主要还是为了将表单信息的注入到class组件的props中。
高阶函数嵌套高阶组件,传递配置参数后,再传入包裹的组件
Form.create = (options = {}) => {
// options todo
return (WrappedComponent) => {
return class extends React.Component {
...
addtionaProps = () => {
return {
getFieldDecorator: this.getFieldDecorator,
setFieldsValue: this.fieldStore.setFieldsValue,
getFieldValue: this.fieldStore.getFieldValue,
validateFields: this.validateFieldsWraped,
resetFields: this.fieldStore.resetFields,
store: this.fieldStore.store,
isSubmitting: this.submitting(),
}
}
render() {
return {
<WrappedComponent
{...this.props}
{...this.addtionalProps()}
/>
}
}
}
}
}
表单项收集,也是通过高阶组件代理,代理表单触发方法,通过forceUpdate触发更新,此外fieldStore对表单的字段初始化、更新、状态做统一的管理控制
getFieldDecorator = ({
name,
rules = [],
initialValue,
trigger = 'onChange',
valueFormat,
valueKey = 'value'
}) => {
// todo... 表单仓库初始化, 注册字段和校验
return (ComponentF) => {
const triggerAction = async (e) => {
ComponentF.props?.onChange(e)
let actionValue = e
if (valueFormat) actionValue = await valueFormat(e)
this.fieldStore.dispatchStore('name', actionValue)
this.forceUpdate()
}
...
const propsNew = {
...ComponentF.props,
}
propsNew[value] = this.fieldStore.getFieldValue(name)
propsNew[trigger] = triggerAction
return React.cloneElement(ComponentF, propsNew, ComponentF.props.children)
}
}
高阶组件的更新过程
- 表单的
trigger直接更改表单仓库字段值 - 触发最外层高阶函数更新,获取最新表单仓库
- 表单项通过高阶组件同步最新的数据
缺陷:高阶函数需要更多的执行成本,每次都需要创建新的表单项
优点:props的注入,每次更新fieldsValue等API获取的数据和视图是完全同步的
hooks的form实现
关键的两个点在
useForm和context共享, useForm通过useRef保存表单仓库的引用
function useForm(
form?: IFormInstanceAPI,
defaultFormValue = {},
): IFormInstanceAPI {
const formRef: { current: IFormInstanceAPI | null } = useRef(null)
const [, forceUpdate] = useState()
useEffect(
function () {
if (formRef.current) {
formRef.current.setFields(defaultFormValue)
}
},
[defaultFormValue],
)
if (!formRef.current) {
if (form) {
formRef.current = form
} else {
const formStoreCurrent = new FormStore(forceUpdate, defaultFormValue)
formRef.current = formStoreCurrent.getForm()
}
}
return formRef.current
}
context通过Provider和useContext实现FormItem共享formStore
function Form(
props: FormProps,
ref: React.ForwardedRef<IFormInstanceAPI>,
): JSX.Element {
const formInstance = useForm(form, initialValues)
// todo...注册表单原生提交方法,暴露组件实例
return (
<form ...>
<FormContext.Provider value={formInstance}>
{children}
</FormContext.Provider>
</form>
)
}
function FormItem(props: FormItemProps) {
const formInstance = useContext<IFormInstanceAPI>(FormContext)
// ...todo
}
优势:每次的更新成本控制在formItem内,减少了更新成本
缺陷:每次更新fieldsValue等API获取的数据和视图不一定同步,需要在trigger方法里面额外设置state或forceUpdate来获取到最新的数据