厉害在哪里?
- 复杂表单
- 结构复杂
- 联动复杂
- 事件复杂
- 高性能
- 需要
表单问题
- 通过effects设置的selfErrors,其他部分怎么做到不覆盖?
- selfErrors是收集型的?其他每个设置的rule,有自身的是否error
- 整个表单的errors,是收集上来的?
- 整个path,如果遇到array,object如何处理,尤其是他们还有rules
- 封装自定义组件如何融入到表单中,由其又是一个array或object类型时
需要注意的点
- 重新render,新的配置不生效
本篇大纲
- formily的feature,为何解决了过去动态表单造轮子的过程
- formily如何做到的高性能?
- formily整体架构,职责以及如何协作的
- formily如何实现reactive?
- 对比那些React的方案,formik,rc-field-form等
- 有哪些问题(联动校验能力)
- 更多formily特征
- 几个核心部分的实现
- Form和Field的model,value和error分开存储?
- effects的实现
- 原Antd组件怎么封装的
- 背后React的树结构怎么做的?
- 对reactive的思考,
- 重新思考表单的模型特征是什么?复杂或特殊在哪里?创造的formily和rc-field-form的模型
状态管理大纲
- 阅读的几篇文章
- 从本质来讲,reactive和不可变的差异,运行模式是什么?代码设计模式上。复杂应用复杂在哪里?
本质上差别
- 数据不是立刻变化的?
- 事件(数值)监听驱动模式 VS 主动式的链式调用
- 集中式管理 VS
几个核心问题的思考
- 为什么主动式的链式调用,在复杂场景更好?复杂场景模式是如何的?以一个vscode中操作为例,如
- 被动式的驱动模式问题
- 为什么不可变不好做主动链式调用?因为数据没有立即更新,供其他的依赖方所使用?
- zustand我倒是用的主动链式调用,是立即更新,通过get获得最新的值
- 关于界面的更新?reactive是数据更新后立刻更新还是?
被动式的数据变化驱动模式问题
1 如果action之后还有行为,尤其是不同的行为,不可控。如orgId变更后,重新请求刷新组织列表,然后可能 1 2 。如果也改成主动链式调用,就要不断传参数,因为状态值是异步更新的。 2 可能响应的代码比较分散,分散在各组件中,看不到全部,修改容易出问题
在简单模式下比较好,分散代表了自制,不需要销毁数据等各种处理
reactive的优势,哪些是用原生的immer无法实现的?为何field-form也自己的一套store
effects ? onFieldRect?but实现了dependices。
如果用reactive去实现field-form是否更简单,不用自己实现这套store,dispatch,publish能力了
现有React的状态管理模式
核心设计思路
过去各种动态表单的方案(使用json来描述表单)层出不穷,但是总有这样那样的问题,如动态性不足,或复杂树结构表单能力不够。但是Formily都很好的解决了这些问题。
我总结下是有了以下的设计才很好的解决了这些问题。有点类似于我们理解的DOM世界,除了提供组件,还提供了js进行修改操作能力。
外部JS获取和操作Field能力 | 通过form或field,js可以获取实例,进而进行get和set |
---|---|
Field的充血模型 | 包括了字段的value , error 等树形,还包括了compoent``componentProps``decotator 等状态,可以依据此来修改一切。 |
完善的表单事件 | |
FormPath能力 | 支持相对,绝对路径。通过query可以获取field实例,进而get和set |
复杂数据嵌套 | - 复杂的数据结构能力,支持数组,对象,并且可以互相随意嵌套 |
顶层实现思路
- 底层定义Form相关的模型。有Form,Field,ArrayField,Query
- 在React层通过实现Form,Field的React组件,支持JSX写法来完成底层Field的树逻辑,和组件的连接。
- 组件库层,拿Antd为例子,
1 先看第一部分,底层的Field模型。
更多可以看packages/core/src/__tests__/form.spec.ts
文件的测试用例
import {
createForm,
setValidateLanguage,
} from '@formily/core';
const form = createForm();
// 创建一个field
// 当前组件的路径 = basePath + name, 就形成了整个嵌套的路径
const field = form.createField({basePath, name, initialValue, value, component, decorator, ...});
// field更新的过程
field.onMount(); // 挂在上
field.onInput('new value'); // 修改value的值
field.value // new value 获取value的值
field.path //
field.onUnmount(); // 取消挂载
// 表单的
form.validate
form.query
form.getState
form.setState
form.reset
form.setValuesIn
2 再看如何和React的连接
如何构造field的树结构,field的注册和销毁。
Form Field层实现思路 更新过程
1 从React的change事件,到值的变更,值的验证,到表单事件触发执行effects,再执行一些逻辑。
2 再到React组件的更新逻辑,React之前通过observer
绑定了值的更新逻辑
Form的执行逻辑
constructor
makeObservable // 让一些值,如value编变的响应式
onInput
1 修改状态,value,modified ->
会自动触发初始化时绑定的value变更事件, ON_FIELD_VALUE_CHANGE
根据其他字段判断,是onInput还是直接修改导致的值变更,是否执行validate函数
2 主动触发form事件 ->
ON_FIELD_INPUT_VALUE_CHANGE
ON_FORM_INPUT_CHANGE
3 执行validate函数
React层面的更新逻辑
import { observer } from '@formily/reactive-react'
//
const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
}
export const ReactiveField = observer(ReactiveInternal, {
forwardRef: true,
})
Form Field层实现思路 状态保存
- value保存到form上,为什么?
- errors相关状态保存到field上,为什么?
关于MVVM的思考
这里面是如何区分的?
整个架构
- reactive 底层响应式
- reactive-react
- core 核心
- form
- field field arrayField objectField
- effects
- validator
- query
- formPath
- react 连接view层
- Field
- hooks
- connect
- observer
- formily-antd
- reactive 提供了底层数据响应式能力。core层在reactive的协助下,事件,effects都非常爽
- 整个核心层即core层,model层,提供了完整的创建,修改删除,监听数据的能力。
- formily-react只是用来连接react,变更
为什么过去的
Core
Form Model
FormPath
Field Model
Feedback
- Field
- feedbacks
- setFeedback
- queryFeedbacks
- selfErrors
- selfWarnings
- selfSuccesses
- form
- queryFeedbacks
set selfErrors(messages: FeedbackMessage) {
this.setFeedback({
type: 'error',
code: 'EffectError',
messages,
})
}
export interface IFieldFeedback {
triggerType?: FieldFeedbackTriggerTypes
type?: FieldFeedbackTypes
code?: FieldFeedbackCodeTypes
messages?: FeedbackMessage
}
internals updateFeedback
- validator校验,以及effect设置error,如何隔离,不覆盖?怎么样的覆盖关系?set的时候
通过 triggerType
, code
,type
来确定唯一的错误,类似于组成一个uniqueKey
。设置错误时,唯一的错误存在则覆盖,如果不存在则新增。
form是如何query的?
几个helper属性和方法的背后
响应式丢失的问题
ArrayField
内部的存储
// 暴露了field
field.value.map(item, index)
FormGraph
Feedbacks
Reactive
为什么要用Reactive?
- 能够写被动联动式语法?
- 更好的联动,修改数据,然后和数据有关联的重渲染
- 为了更高的性能,只重渲染需要的部分?
怎么写?以实现一个自定义组件为例子
这是一个局部的title的例子,获取当前字段的index,然后标题展示 第${index}条
,在前面的条目被删除,影响当前index时,需要动态的更新。
- useField获取field上下文,背后是一个Context提供的上下文
- observer 使这个Component 实现响应式
import { Field, observer, useField, useFormEffects } from '@formily/react';
const ReviewCycleItemTitle = observer(() => {
const field = useField();
const pathSegments = field.path.segments;
const index = pathSegments[pathSegments.length - 2];
return `第${ index + 1}条`;
});
猜测observer背后的实现
即做了一个hoc包裹
function observer(){
const [, setForceRender] = setState({});
const currentFunc = fn;
// 开始收集依赖
const children = fn();
// 结束收集依赖
if(依赖变化){
setForceRender({});
}
return children;
}
背后实现
packages/reactive-react/src/observer.ts
packages/reactive-react/src/hooks/useObserver.ts
packages/reactive/src/tracker.ts
export class Tracker {
track: Reaction = (tracker: Reaction) => {
try {
// 开始 开始了依赖收集 收集了tracker中的依赖,如果将来依赖有变化,则执行track函数。
batchStart()
reactionStack.push(this.track);
this.results = tracker()
} catch(e) {
// 结束 结束了依赖收集
reactionStack.pop();
batchEnd()
}
return this.results;
}
packages/reactive/src/annotations/observable.ts
核心的响应式实现
import {
bindTargetKeyWithCurrentReaction,
runReactionsFromTargetKey,
} from '../reaction'
// createAnnotatio是做什么?
export const observable: IObservable = createAnnotation(
({ target, key, value }) => {
function get() {
bindTargetKeyWithCurrentReaction()
},
function set() {
runReactionsFromTargetKey({});
}
}
packages/reactive/src/reaction.ts
这个依赖的数据结构是如何的?
背后有两个map,为何要两个map?
addRawReactionsMap 即把当前的reaction函数,加入到这个target的依赖map上
// RawReactionsMap 以
{
[target]: {
[key]: [reaction, reaction, ...]
}
}
// const addReactionsMapToReaction = (
// 当前的reaction和当前的observable数据建立起依赖关系
bindTargetKeyWithCurrentReactio(){
const current = ReactionStack[ReactionStack.length - 1]
if (current) {
DependencyCollected.value = true
addReactionsMapToReaction(current, addRawReactionsMap(target, key, current))
}
}
export const runReactionsFromTargetKey = (operation: IOperation) => {
runReactions(target, key)
}
const runReactions = (target: any, key: PropertyKey) => {
const reactions = getReactionsFromTargetKey(target, key)
}
const getReactionsFromTargetKey = (target: any, key: PropertyKey) => {
const reactionsMap = RawReactionsMap.get(target)
}
Field的reactive
makeObservable 方法
makeReactive 方法
React
- Field
- ReactiveField
Field
Antd
- FormItem
接收了输入的props,field,用field来获取相关的feedbackStatus和feedbackText
借鉴
https://core.formilyjs.org/zh-CN/guide/mvvm 介绍了mvvm模型
可能是你见过最专业的表单方案—-解密Formily2.0 - 知乎
不好意思,我又重构了Formily内核 - 知乎
Formily2.0思维导图(附) - 知乎
Formily实践 - 知乎