⚡React学习之打造具备局部渲染特性的高性能Form表单组件

571 阅读2分钟

前提:

在前端日常开发中,表单组件尤为常见,很可能一个页面就是一个表单,附带了十几二十个表单项,每一个表单里的渲染内容也尤为复杂,甚至可能嵌入表格等。可见,对于一个表单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.png

代码实现

如何消费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>
  )
}

示例

ezgif.com-gif-maker.gif