厉害在哪里?

  • 复杂表单
    • 结构复杂
    • 联动复杂
    • 事件复杂
  • 高性能
    • 需要

表单问题

  • 通过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的状态管理模式

  • context

  • Formily

核心设计思路

过去各种动态表单的方案(使用json来描述表单)层出不穷,但是总有这样那样的问题,如动态性不足,或复杂树结构表单能力不够。但是Formily都很好的解决了这些问题。

我总结下是有了以下的设计才很好的解决了这些问题。有点类似于我们理解的DOM世界,除了提供组件,还提供了js进行修改操作能力。

外部JS获取和操作Field能力 通过form或field,js可以获取实例,进而进行get和set
Field的充血模型 包括了字段的value, error等树形,还包括了compoent``componentProps``decotator等状态,可以依据此来修改一切。
完善的表单事件
FormPath能力 支持相对,绝对路径。通过query可以获取field实例,进而get和set
复杂数据嵌套
- 复杂的数据结构能力,支持数组,对象,并且可以互相随意嵌套

顶层实现思路

  1. 底层定义Form相关的模型。有Form,Field,ArrayField,Query
  2. 在React层通过实现Form,Field的React组件,支持JSX写法来完成底层Field的树逻辑,和组件的连接。
  3. 组件库层,拿Antd为例子,

1 先看第一部分,底层的Field模型。

更多可以看packages/core/src/__tests__/form.spec.ts文件的测试用例

  1. import {
  2. createForm,
  3. setValidateLanguage,
  4. } from '@formily/core';
  5. const form = createForm();
  6. // 创建一个field
  7. // 当前组件的路径 = basePath + name, 就形成了整个嵌套的路径
  8. const field = form.createField({basePath, name, initialValue, value, component, decorator, ...});
  9. // field更新的过程
  10. field.onMount(); // 挂在上
  11. field.onInput('new value'); // 修改value的值
  12. field.value // new value 获取value的值
  13. field.path //
  14. field.onUnmount(); // 取消挂载
  15. // 表单的
  16. form.validate
  17. form.query
  18. form.getState
  19. form.setState
  20. form.reset
  21. form.setValuesIn

2 再看如何和React的连接

如何构造field的树结构,field的注册和销毁。

Form Field层实现思路 更新过程

1 从React的change事件,到值的变更,值的验证,到表单事件触发执行effects,再执行一些逻辑。
2 再到React组件的更新逻辑,React之前通过observer绑定了值的更新逻辑

Form的执行逻辑

  1. constructor
  2. makeObservable // 让一些值,如value编变的响应式
  3. onInput
  4. 1 修改状态,valuemodified ->
  5. 会自动触发初始化时绑定的value变更事件, ON_FIELD_VALUE_CHANGE
  6. 根据其他字段判断,是onInput还是直接修改导致的值变更,是否执行validate函数
  7. 2 主动触发form事件 ->
  8. ON_FIELD_INPUT_VALUE_CHANGE
  9. ON_FORM_INPUT_CHANGE
  10. 3 执行validate函数

React层面的更新逻辑

  1. import { observer } from '@formily/reactive-react'
  2. //
  3. const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
  4. }
  5. export const ReactiveField = observer(ReactiveInternal, {
  6. forwardRef: true,
  7. })

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
  1. set selfErrors(messages: FeedbackMessage) {
  2. this.setFeedback({
  3. type: 'error',
  4. code: 'EffectError',
  5. messages,
  6. })
  7. }
  8. export interface IFieldFeedback {
  9. triggerType?: FieldFeedbackTriggerTypes
  10. type?: FieldFeedbackTypes
  11. code?: FieldFeedbackCodeTypes
  12. messages?: FeedbackMessage
  13. }

internals updateFeedback

  • validator校验,以及effect设置error,如何隔离,不覆盖?怎么样的覆盖关系?set的时候

通过 triggerType, code,type 来确定唯一的错误,类似于组成一个uniqueKey。设置错误时,唯一的错误存在则覆盖,如果不存在则新增。

  • form是如何query的?

  • 几个helper属性和方法的背后

  • 响应式丢失的问题

ArrayField

内部的存储

  1. // 暴露了field
  2. field.value.map(item, index)

FormGraph

Feedbacks

Reactive

为什么要用Reactive?

  • 能够写被动联动式语法?
  • 更好的联动,修改数据,然后和数据有关联的重渲染
  • 为了更高的性能,只重渲染需要的部分?

怎么写?以实现一个自定义组件为例子

这是一个局部的title的例子,获取当前字段的index,然后标题展示 第${index}条,在前面的条目被删除,影响当前index时,需要动态的更新。

  • useField获取field上下文,背后是一个Context提供的上下文
  • observer 使这个Component 实现响应式
  1. import { Field, observer, useField, useFormEffects } from '@formily/react';
  2. const ReviewCycleItemTitle = observer(() => {
  3. const field = useField();
  4. const pathSegments = field.path.segments;
  5. const index = pathSegments[pathSegments.length - 2];
  6. return `第${ index + 1}条`;
  7. });

猜测observer背后的实现

即做了一个hoc包裹

  1. function observer(){
  2. const [, setForceRender] = setState({});
  3. const currentFunc = fn;
  4. // 开始收集依赖
  5. const children = fn();
  6. // 结束收集依赖
  7. if(依赖变化){
  8. setForceRender({});
  9. }
  10. return children;
  11. }

背后实现

packages/reactive-react/src/observer.ts
packages/reactive-react/src/hooks/useObserver.ts
packages/reactive/src/tracker.ts

  1. export class Tracker {
  2. track: Reaction = (tracker: Reaction) => {
  3. try {
  4. // 开始 开始了依赖收集 收集了tracker中的依赖,如果将来依赖有变化,则执行track函数。
  5. batchStart()
  6. reactionStack.push(this.track);
  7. this.results = tracker()
  8. } catch(e) {
  9. // 结束 结束了依赖收集
  10. reactionStack.pop();
  11. batchEnd()
  12. }
  13. return this.results;
  14. }

packages/reactive/src/annotations/observable.ts

核心的响应式实现

  1. import {
  2. bindTargetKeyWithCurrentReaction,
  3. runReactionsFromTargetKey,
  4. } from '../reaction'
  5. // createAnnotatio是做什么?
  6. export const observable: IObservable = createAnnotation(
  7. ({ target, key, value }) => {
  8. function get() {
  9. bindTargetKeyWithCurrentReaction()
  10. },
  11. function set() {
  12. runReactionsFromTargetKey({});
  13. }
  14. }

packages/reactive/src/reaction.ts

这个依赖的数据结构是如何的?

背后有两个map,为何要两个map?

addRawReactionsMap 即把当前的reaction函数,加入到这个target的依赖map上

  1. // RawReactionsMap 以
  2. {
  3. [target]: {
  4. [key]: [reaction, reaction, ...]
  5. }
  6. }
  7. // const addReactionsMapToReaction = (
  1. // 当前的reaction和当前的observable数据建立起依赖关系
  2. bindTargetKeyWithCurrentReactio(){
  3. const current = ReactionStack[ReactionStack.length - 1]
  4. if (current) {
  5. DependencyCollected.value = true
  6. addReactionsMapToReaction(current, addRawReactionsMap(target, key, current))
  7. }
  8. }
  9. export const runReactionsFromTargetKey = (operation: IOperation) => {
  10. runReactions(target, key)
  11. }
  12. const runReactions = (target: any, key: PropertyKey) => {
  13. const reactions = getReactionsFromTargetKey(target, key)
  14. }
  15. const getReactionsFromTargetKey = (target: any, key: PropertyKey) => {
  16. const reactionsMap = RawReactionsMap.get(target)
  17. }

Field的reactive

makeObservable 方法
makeReactive 方法

React

  • Field
  • ReactiveField

Field

component
decorator

Antd

  • FormItem

接收了输入的props,field,用field来获取相关的feedbackStatus和feedbackText

借鉴

https://core.formilyjs.org/zh-CN/guide/mvvm 介绍了mvvm模型
可能是你见过最专业的表单方案—-解密Formily2.0 - 知乎
不好意思,我又重构了Formily内核 - 知乎
Formily2.0思维导图(附) - 知乎
Formily实践 - 知乎