Form对你的组件做了什么?

文章内容及代码基于antd4.x,文末附上源码,配合源码阅读效果更佳。阅读本文你会收获:一.Form的核心原理。二.手把手带你写一个简易版的Form

Antd Form核心原理

image.png    在antd Form中,存在一个高于所有Field组件的Store仓库用于存储表单值。通过向Field的子组件中传递Value以及onChange实现Store的与组件值的双向绑定。当Store的值发生变化,对应变动name的Field的Value跟随变动并触发Field的forceUpdata以更新视图。

一. Field注入的3个参数

   众所周知FormItem会默认往我们的组件Props属性中放入3个参数:id, value, onChange。要想弄清楚form的原理,就需要先了解这3个参数。

1.id
  <Form.Item
        name='name'
        rules={[{ required: true, message: 'Missing first name' }]}
      >
        <Form.Item
          name={['name','firstName']}
          rules={[{ required: true, message: 'Missing first name' }]}
        >
          <MyInput></MyInput>
        </Form.Item>
    </Form.Item>
复制代码

  如上嵌套FormItem,最终在我们自定义组件MyInput的props中会得到id:'name_firstName'。id属性一般场景下并不会被用到。在一些特殊场景下,我们可以通过id进行解析获取到当前字段的pathName来进行后续操作。

2.value

  表单Store数据变动时,form会往对应namePath的Field中传递新的Value值以更新子组件的值。

3.onChange(trigger)

  触发器默认值为:onChange。监听子组件变动的触发器,当子组件值发生变动,通过触发器修改Store的值。

二. 手写简易版Form

  基于以上得出原理,我们可以通过实现一个简易版本的Form来加深理解,去掉List、多层字段嵌套、校验功能等功能,实现一个Store与field Value双向响应的form。

UseForm
  let store: Store = {}  // form全局数据仓库
  const fieldEntities: FieldEntities = [] // field实例列表
  
  getFieldsValue()  // 获取store的current value
  notifyObservers(namePath)  // store值变动时,通知对应field forceUpdata
  setFieldsValue(newValue) // 全量赋值store
  registerField(entity) // 每个field实例化时进行注册记录进fieldEntities。
  dispatch({
  type: 'updateValue',
  namePath: 'xx',
  value: 'newFieldValue'
  }) // 用于更新指定name值
  useForm(form) // 绑定form hook
复制代码

  UseForm声明了form的全局数据仓库store以及field实例列表fieldEntities。提供了store仓库的增删改查函数,store仓库被操作后通过bian libianli

FieldContext
const context = React.createContext<FormInstance>({
  getFieldsValue: warningFunc,
  setFieldsValue: warningFunc,
  registerField: warningFunc,
  dispatch: warningFunc,
})

export default context
复制代码

 应用于form全局的上下文属性。

  • getFieldsValue 获取全量Store
  function getFieldsValue(){
    let result: Store = {}
    fieldEntities.forEach((field:any)=>{
       const name = field.name
       result[name] = store[name]?store[name]: undefined
    })
    return result
  }
复制代码

根据field实例列表补充返回值

  • setFieldsValue 设置全量Store
  function setFieldsValue(newValue: Store){
    fieldEntities.forEach((entity)=>{
      entity.onStoreChange()
    })
    return store = newValue
  }
复制代码

遍历实例列表通知field forceUpdata

  • updateValue 更新特定name的值
  function updateValue(namePath:any, value:any){
     store[namePath] = value
     notifyObservers(namePath)
  }
复制代码

每次值变动则通知field更新

Field
  const returnChildNode = React.cloneElement(
    children,
    getControlled(children.props)
  );
  return <React.Fragment>{returnChildNode}</React.Fragment>;
复制代码

 在Field需要对子组件props属性的注入Value以及onChange。

  • getControlled
  function getControlled(childProps: Record<string, any>) {
    const formValue = getFieldsValue();
    const mergedGetValueProps = {
      value: formValue[name],
    };
    const control: Record<string, any> = {
      ...childProps,
      ...mergedGetValueProps,
    };
    control[trigger] = (...args: any[]) => {
      let eventValue = defaultGetValueFromEvent("value", ...args);
      dispatch({
        type: "updateValue",
        namePath: name,
        value: eventValue,
      });
    };
    return control;
  }
复制代码

 通过结构赋值覆盖组件的value以及onChange属性。监听Field子组件的trigger,将修改内容更新至store中。

Form
function Form({ children, form }: FormProps) {
  const [formInstance] = useForm(form);

  const formContextValue: FormInstance = formInstance;
  const wrapperNode = (
    <FieldContext.Provider value={formContextValue}>
      {children}
    </FieldContext.Provider>
  );
  return <div>{wrapperNode}</div>;
}
复制代码

 绑定Form的hook,一个form hook对应一个Form实例。

小结

  antd form巧妙的利用forceUpdata实现了store与Field之间的双向更新,避开了react的state。实例Form之后提供不算多的操作函数,却又足够开发者使用,namPath的数组设置则适配了更加复杂的场景。

  为了让读者更容易理解,本次实现去掉了许多Antd Form的核心功能,诸如:FormList,校验器等。感兴趣的同学可自行参考其源码。

参考链接

github.com/react-compo…

源码仓库

github.com/justworkhar…

猜你喜欢

转载自juejin.im/post/7039744180343537672
今日推荐