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", width: "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'] => 3valueUtil.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之间的协调调用。 Form向FormInstance更新ValidateMessage,传递事件处理函数onValuesChange,onFieldsChange,onFinish,onFinishFailed,setPreserve,setInitalValues,这个初始值是Form level的设置。这一些列都属于初始化的操作,将 Form 的输入转移到FormInstance里面。- Form 有两种使用模式,render Props 以及 ReactElement模式,第一种模式,children:(values,formInstance)=>React.Node,则 Form 会执行这个函数。这种模式下,它属于非 Subscribe模式。
Form有fields: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()里面,Field向formInstance注册自己。 这样就完成了一个简单的流程。
一直跟着源码走,按着用例走,知道了,它能干啥,怎么用,但是有个问题,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的类型。- 如果是
reset,则响应 reset, - 如果是
setField, 只要当自己在 FieldList 里面才响应。 - 如果是
dependenciesUpdate,只要自己的 dependencies 里面包含在 FeildList 里面,才响应。 - 对于其他情况,如果跟自己相关,或者需要更新(由 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,按需刷新。

浙公网安备 33010602011771号