前提:
在前端日常开发中,表单组件尤为常见,很可能一个页面就是一个表单,附带了十几二十个表单项,每一个表单里的渲染内容也尤为复杂,甚至可能嵌入表格等。可见,对于一个表单Form组件,它的性能显得十分重要。
设计:
初步实现:
Form组件通过useState维护state内容,通过useImperativeHandle这个hooks暴露给用户,用户使用useRef().current去拿到state。
问题:
FormItem组件的children比如input框改变内容时,需要告知Form组件中的state进行更新,即Form组件进行一次setState,这将导致了Form组件及其所有后代组件都进行一次rerender
,对于简单的表单,这其实没什么,但当场景像前提中所述,这个页面性能可能就要被用户吐槽了。
思考:
有没有一种可能,我们的最小渲染单元是被FormItem表单项组件呢?我们又应该如何去做呢? 其实不难,对于毫无逻辑关系的两个A,B组件而言,在渲染层面本就不应该有所耦合。
而导致上述问题的原因就是它们的共同Form父组件,所以我们把状态管理这一步,移交至每个FormItem组件自身,就能做到局部渲染
的高性能表现了。
再思考一下,之前,我们是把表单的state放在Form组件透给用户,只维护了一份state,现在,我们可能需要维护2份state,即每一个FormItem的state,以及透给用户的state,很简单,提供一个useForm来进行状态管理即可,用户使用Form组件之前,需要调用useForm拿到整个表单的state
。
实现思路
代码实现
如何消费Form组件?
export default function FormTest({}: Props) {
const form = useForm()
const handleClick = () => {
console.log(form.getFormVal())
}
return (
<div>
<Form form={form}>
<FormItem field="name" label="姓名">
<input type="text" />
</FormItem>
<FormItem field="age" label="年龄">
<input type="text" />
</FormItem>
</Form>
<button onClick={handleClick}>Confirm</button>
</div>
)
}
useForm.ts
const useForm = () => {
const state: Record<string, any> = {}
const getFormVal = () => {
return state
}
const getFieldVal = (name: string) => {
return state[name]
}
const setFieldVal = (name: string, value: any) => {
state[name] = value
}
return {
getFormVal,
getFieldVal,
setFieldVal,
}
}
export default useForm
Form.tsx
export const FormContext: any = React.createContext({})
export const Form: React.FC<React.PropsWithChildren<IFormProps>> = ({
children,
form,
}: React.PropsWithChildren<IFormProps>) => {
return (
<FormContext.Provider value={{ form }}>
<div>{children}</div>
</FormContext.Provider>
)
}
FormItem.tsx
export const FormItem: React.FC<React.PropsWithChildren<IFormProps>> = ({
children,
label,
field,
}: React.PropsWithChildren<IFormProps>) => {
console.log('FormItem组件渲染了!')
const [state, setstate] = React.useState('')
const formContext: any = React.useContext(FormContext)
const fieldContext = {
value: state,
onChange: (e: any) => {
const val = e.target.value
setstate(val)
formContext.form.setFieldVal(field, val)
},
}
React.useEffect(() => {
formContext.form.setFieldVal(field, state)
}, [])
return (
<div>
<div>
{label}:{React.cloneElement(children as any, fieldContext)}
</div>
</div>
)
}