Rc Form 学习笔记

学习 form 的时候遇到的一些问题

  • 使用 FormProvider 的时候发现onFormChange被触发两次。
<FormProvider
  validateMessages={myMessage}
  onFormChange={(name, { changedFields, forms }) => {
    // 每次form 有更新,这个方法被触发了两次?????
    // 一次是 valueUpdate, 一次是 formvalidation。
    console.log("changes from: ", name, changedFields, forms);
    if (name === "first") {
      forms.second.setFields(changedFields);
    }
  }}
  onFormFinish={(name, { values, forms }) => {
    console.log("finish form: ", name, values, forms);
    if (name === "second") {
      forms.first.setFieldsValue(values);
    }
  }}
>
  <div style={{ display: "flex",  "100%" }}>
    <Form1 />
    <Form2 />
  </div>
</FormProvider>

Field

  • FieldContext实际上是FormInstance,由 Form 通过 Context 传递给 Field。
  • Field 被 FC 封装,主要设置 key,name,取得 FieldContext。实际的内容在类组件内。
  • Field 设置初始值,通过调用 hook 里面的 initialize 方法。
  • Field 的 children 是 ReactNode 或者 (control,meta,formInstance)=>{child:ReactNode,isFunction:boolean}的方法。
  • 首先,如果 children 是方法,那么执行该方法,然后将结果递归调用getOnlyChild,如果不是方法,则将 children 摊平通过rc-util/toArray,原理是,如果是空则跳过,如果是Fragment则递归它的children,要不就push到结果结合中。结果返回 childrenlist 或者第一个 children。
  • 构建 childNode,如果上一步isFunction,则上一步的返回就是 child,如果isValidateElement(child),修改它的输入,getControlled(child.props), 包括:1.从 form 中拿 field 值,变成 controlled 模式,2.提供trigger默认是onChange方法,(响应 Field child 组件值的变化,)更新 Field 状态,touch/dirty/validating,触发 onMetaChange,从onChange中获取值,dispatch值到 Form。然后再触发原来的trigger方法。这里有个技巧,先暂存原理的 trigger 方法,然后用自定义的 trigger 方法复写,在自定义的方法最后再调用原来的方法。 3.按顺序触发 validateList
const originTriggerFunc: any = childProps[trigger];

const control = {
  ...childProps,
  ...mergedGetValueProps(value),
};

// Add trigger
control[trigger] = (...args: EventArgs) => {
  // Mark as touched
  this.touched = true;
  this.dirty = true;

  this.triggerMetaEvent();

  let newValue: StoreValue;
  if (getValueFromEvent) {
    newValue = getValueFromEvent(...args);
  } else {
    newValue = defaultGetValueFromEvent(valuePropName, ...args);
  }

  if (normalize) {
    newValue = normalize(newValue, value, getFieldsValue(true));
  }

  dispatch({
    type: "updateValue",
    namePath,
    value: newValue,
  });

  if (originTriggerFunc) {
    originTriggerFunc(...args);
  }
};
  • 将上一步的 children 用 Fragment 封装。 提供 key,key={resetCount}
  • componentDidMount里面向 Form 注册 Field。shouldUpdate会在挂载后再次触发一次 render。

useForm

Field 的 children 触发 onChange,由 Field 调用dispatch()方法,我们来捋捋这个方法会干些什么,我们先谈谈 updateValue,这个事件

  • 首先进入useForm.updateValue(namepath,fieldValue),首先将 fieldValue 更新到整个 value 中,
  • 调用 notifyObservers(): 这个方法则遍历所有的 field,然后,调用field.onStoreChange方法。这个方法主要是触发 forceRender,这里面有一种场景是由shouldUpdate来控制是否需要 forceRender,在setField/默认info.type下会触发这个shouldUpdate执行。1.reset,2.对没有 name 的 field 进行 setField,并且 shouldUpdate 返回 true, 3.dependenciesUpdate,当前 field 依赖它,4.field 属于这次更新的 field 成员中一个,并且 shouldupdate 返回 true.这些条件都会触发forceRender
  • 调用useForm.getDependencyChildrenFields(namePath)获取所有依赖该 field 的孩子 field.这个里面存在一个递归调用,获得,isFieldDirty 的所有孩子孙子 fieldNamePath,
  • 遍历这些依赖的孩子 Field, 调用useForm.validateFields(),它会遍历所有的 fieldEnitties,找到合适的(如果没有提供目标 field,就是所有的,如果有目标,则只找目标),调用目标field.validateRules(),结果存到promiseList里面,然后这个 promiseList 会用Promise.allSettled()。验证的 promise 并没有直接插入promiseList,而是预先做了 error/warning/success 处理。
  • field.validateRules(),首先获取field.getRules(),它通过 field.props.rules 获取,可能是Rule也可能是(formInstance)=>RuleObject. 依据triggerName过滤 Rule,调用validateUtil.validateRules()验证,返回 promise. 它先逻辑validateFirst是 Rules 里面只要有 reject, 就整个 reject,否则,所有成功 resolve([]),parallel validate,
  • 对于summaryPromise,会先调用notifyObservers(type:'validateFinish'),然后对有 error 的 field,调用this.triggerOnFieldsChange(resultNamePathList, results)
  • 接着调用notifyObservers(),发布type: 'dependenciesUpdate'事件。调用onValuesChange,最后再调用this.triggerOnFieldsChange([namePath, ...childrenFields])

List 聊聊这个

它实际上是接管了 List 里面每个选项的 name,key这两个属性,同时提供了add/remove/move这三个方法

我们来总结一下一些方法 valueUtil

  • valueUtil.setValues<T>(store: T, ...restValues: T[]): T , 这个方法其实时lodash.merge(target,...source), 它干啥呢。给个它自己的例子,({ a: 1, b: { c: 2 } }, { a: 4, b: { d: 5 } }) => { a: 4, b: { c: 2, d: 5 } }, ({a: [{ b: 2 }, { d: 4 }]},a: [{ c: 3 }, { e: 5 }])=>{a: [{ b: 2, c: 3 }, { d: 4, e: 5 }] }
  • valueUtil.getValue(store:Store,namePath:InternalNamePath),类似于lodash.get(object,path,default)。给个例子,自己体会, {a:[{b:{c:3}}]},['a',0,'b','c'] => 3
  • valueUtil.setValue(store,Store,namePath: InternalNamePath,value: StoreValue,removeIfUndefined = false,),类似于lodash.set(object, path, value)
  • valueUtil.isSimilar(),这个方法时比较第一层的两个对象是否一样。
  • valueUtil.cloneByNamePathList(store: Store, namePathList: InternalNamePath[]): Store,这个方法是从 store 中提取特定的属性,同时保持结构, 类似于pick。跟lodash.pick(object,[paths])
  • valueUtilmove<T>(array: T[], moveIndex: number, toIndex: number) 把一个元素移动到另一个位置,会产生一个新的数组。

asyncUtil

  • allPromiseFinish(promiseList: Promise<FieldError>[]): Promise<FieldError[]>这个方法其实就是Promise.allSettled(promiseList), 这个是es2020里面的方法。所有的 promise 都结束后再返回 resolve/reject(results[])

NameMap

它其实就是一个 map, 它的特殊是,它的 key 是 string|number[],也就是 form 的 namePath,它内部调用normalize方法,将路径序列化,同时也可以反序列化。反序列化的时候可以区分 string 与 number。它的主要功能是将 form 里面的 field,按照路径为 key,进行存放,实现缓存。

validateUtil 这个主要是用来做验证的。

  • validateRules(namePath: InternalNamePath,value: StoreValue,rules: RuleObject[],options: ValidateOptions,validateFirst: boolean | 'parallel',messageVariables?: Record<string, string>,) 它主要用来验证 field,上面所有的 validations. 首先对 rules 排序,warning 优先。
  • validateRule(name: string,value: StoreValue,rule: RuleObject,options: ValidateOptions,messageVariables?: Record<string, string>,): Promise<string[]>
  • 这里面有个validateFirst,true: 表示,按顺序来验证,挂了,就结束,false: 表示同时并行验证,Promise.all() 来绝对返回结果。

我们来谈谈 Form 从创建,到有值的改变,到提交,这个过程中的调用链。

  • 首先我们得有一个FormInstance,两种方法获取,一个是主动调用useForm(),另一个就是通过 ref 拿到Form 内部调用useForm() 创建的。同时,Form还把自己注册到FormContext 里面,以方便多个Form之间的协调调用。
  • FormFormInstance 更新ValidateMessage,传递事件处理函数onValuesChange,onFieldsChange,onFinish,onFinishFailed,setPreserve,setInitalValues,这个初始值是Form level的设置。这一些列都属于初始化的操作,将 Form 的输入转移到FormInstance 里面。
  • Form 有两种使用模式,render Props 以及 ReactElement模式,第一种模式,children:(values,formInstance)=>React.Node,则 Form 会执行这个函数。这种模式下,它属于非 Subscribe模式。
  • Formfields:FieldData[]的输入参数,它表示的是 fields 的状态跟值(touched,value,name,error .etc) 这个值会依据formInstance, fields来进行缓存。
  • 构建FieldContextValue, 它其实就是 FormInstance + validateTrigger,通过封装到 FieldContext 传递给 Field,这也是为什么 Field 里面会拿到formInstance。 它用了 memo 进行缓存。
  • Form 以及Component 参数来绝对直接返回上一步封装的 children, 还是 通过form/Componnet 来封装。
  • Form 里面会调用setCallbacks,它会将onValuesChange,onFieldsChange,onFinish,onFinishFailed托管到useForm里面去。

接下来我们进入到 Fields 里面的创建过程。

首先呢,Fields 对于 Form 来说是平层化了,它的立体化是通过 name 来体现的,name:string|number[]

  • Field 是通过一个方法套在了 Field 类上面,方法的作用就是接管key,name,fieldContext。把它们作为参数传递给 Field Class。
  • Field 的children:React.ReactNode|(control:ChildProps,meta:Meta,context:FormInstance)=>React.ReactNode, 如果 children 是 function, 则执行方法,再次调用 getOnlyChild() 方法,递归。执行 children 方法的时候有个getControlled(), 这个方法功能是将 children 控件变成 control 模式,也就是说给它传递value, 接受它传递上来的valuechange,通过valuePropName,trigger这两个属性控制的。它拿到更新后的 value 后,通过dispatch('updateValue')转发到formInstance 里面去。完了,触发forceUpdate,页面刷新,这个方法又可以拿到更新后的值,再传递给 children 组件,从而实现 controlled 模式下的双向绑定。 这个方法还包括另一个功能,通过 dispatch('validateField')来触发 validation,它是通过在triggerName里面放置处理函数来实现,它会迭代 Field 里面所有的 validation,来触发验证。
  • Field 依据上一步返回的 ChildNode, 如果不是 renderProps, 则调用getControlled(),把 children 变成 controlled 模式。然后返回 childrenNode.
  • 上面主要是谈论的render()方法,其中关于valueChange,trigger,triggerName只是用来定义的方法,方法的执行还没有执行。然后在ComponentDidMount()里面,FieldformInstance注册自己。 这样就完成了一个简单的流程。

一直跟着源码走,按着用例走,知道了,它能干啥,怎么用,但是有个问题,Form/Field/List,它的职责是什么,为什么要它,它是怎么做的。我的理解是,

  • 它的职责就是,将 Form 的值,保存到一个对象里,或者从一个对象里将值分发到 Dom 里面,同时保证软件运行过程中的同步,validaiton 的及时触发。
  • 它是怎么做的,将每个input变成controlled模式,通过trigger+valuePropName,拿到 input 改变的值。trigger默认是onChange 事件, valuePropName默认是value也就是(event.target.value)。这样我们就不用自己定义事件了。而验证条件的触发则是通过validateTrigger默认是onChange事件来触发的。

来聊聊如果一个 Field 的值发生了改变,那么整个 Form 是如何运行的。我们假设trigger是默认值也就是onChange

  • 当 Dom 发出onChange事件,Field 首先更新touched,dirty,然后激发this.triggerMetaEvent();,这个方法主要激发onMetaChange事件,它是由 FieldProps 来的。
  • Field 然后拿到onChange里面的值,可能会normalize?.(value),然后调用useForm.dispatch({type:'updateValue'}),最后调用我们自己添加到 Field 上面的onChange事件。
  • useForm.dispatch里面会调用this.updateValue(namePath, value);,它首先把 value 保存到整个 Form 的 data 里面,然后调用
this.notifyObservers(prevStore, [namePath], {
  type: "valueUpdate",
  source: "internal",
});
  • notifyObservers这个方法之前也讲过,主要是遍历所有的 FieldList,然后调用它们的onStoreChange(prevStore, namePathList, mergedInfo)
    • Field.onStoreChange,这个函数主要功能是更新 Field meta 状态,响应 Field 上本身定义的事件,刷新页面, 首先看自己在不在 这次触发改变的 FielList 里面,对于,if (info.type === 'valueUpdate' && info.source === 'external' && prevValue !== curValue),则更新自己 Field Meta 状态。接下来,判断info.type的类型。
      1. 如果是reset,则响应 reset,
      2. 如果是setField, 只要当自己在 FieldList 里面才响应。
      3. 如果是dependenciesUpdate,只要自己的 dependencies 里面包含在 FeildList 里面,才响应。
      4. 对于其他情况,如果跟自己相关,或者需要更新(由 shouldUpdate 计算)同时(要么没有依赖性,是一个真的 field,有名字,或者,设置了 shouldupdate)则刷新页面。还有如果 shouldupdate===true,一定刷新 Field.
  • 回到useForm.updateValue()这个方法里面接着获取依赖该 field 的所有孩子及孙子,平层化后,调用this.validateFields(childrenFields);
  • 接着对这些依赖项调用notifyObservers
this.notifyObservers(prevStore, childrenFields, {
  type: "dependenciesUpdate",
  relatedFields: [namePath, ...childrenFields],
});
  • 调用注册的 callback 里面的 onValuesChange 方法,然后再调用this.triggerOnFieldsChange([namePath, ...childrenFields]);

如果直接调用useForm.setFieldsValue()则调用链

  • 这种情况比较简单,直接setValues(),然后调用this.notifyObservers(prevStore, null, {type: 'valueUpdate',source: 'external',});,通知每个 Field,按需刷新。
原文地址:https://www.cnblogs.com/kongshu-612/p/15026931.html