按照 formily
官方文档,最小化复现每个示例。本项目不提供线上预览,请直接在本地运行npm install && npm run start
示例包含 4 个文档:
formily
主文档:https://formilyjs.org/zh-CN@formily/reactive
:https://reactive.formilyjs.org/zh-CN@formily/core
:https://core.formilyjs.org/zh-CN@formily/react
:https://react.formilyjs.org/zh-CN
其中组件采用 @formily/antd-v5
为主文档作演示。不包含 @formily/vue
,技术栈为 React
,在 formily
中两者大同小异
演示分两部分:
- 页面演示:
src/page
目录下,下载安装安装依赖后npm run start
- 单元测试:
src/__test__
目录下,下载安装安装依赖后npm run test
所有示例根据自己理解添加了说明、并根据自己的理解补充示例;在文档最后补充两个作业:
- 一个第三方文档 fishedee 的
formily
总结做一套练习 - 补充一个掘金练习:自增对象
主要的使用的包:
- NodeJS:
v18.13.0
- 脚手架:
Create React App
+react-app-rewired
+customize-cra
- 表单和UI库:
formily
+antd
+antd-style
- 数据模拟:
mocker-api
+mockjs
启动项目前需要先安装一次依赖:
npm install
和官方文档(以下简称“文档”)不同点:
- 采用
antd v5
作为默认演示,文档默认是v4 - 采用
antd-style
作为css-in-js
框架,可以在这里查看详细演示 [查看] - 由于官方提供的
@formily/antd-v5
采用的版本是5.6
,很多 API 已发生变更,因此在演示组件中对部分组件重新修改 - 根据演示文件,根据自己的理解,重新调整了文件和目录结构
- 根据阅读的理解,添加个人附加案例
- 如上所述,
formily v2
的Api会将个人解读,通过源码注释写在源码 - 而
README.md
将会每个演示包含的注解,作为索引简要的罗列出来,方便查找 - 查找方法,每一个内容简介关联着对应示例,运行演示查找对应文件可以找到源码注释
- URL:
/start
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Start.tsx
- 包含章节:
- 快速开始 [查看]
- URL:
/login
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/LoginRegister.tsx
- 包含章节:
- 登录注册 [查看]
包含:
通过Markup Schema
创建登录:
- 验证模式:
createForm.validateFirst
- 字段说明:
SchemaField.String
,及相关属性- 关联反应:
SchemaField.reactions
,主动受控和被动受控、受控依赖更新依赖组件状态
- 关联反应:
- 创建自定义表单组件
通过Json Schema
创建登录
- 包含:
SchemaField.schema
,使用JSON配置表单验证及其优点
通过JSX
创建登录:
- 包括:
jsx
和schema
的不同,以及优缺点
通过Markup Schema
创建注册:
- 组件:
ArrayItems
、Cascader
、DatePicker
、Editable
、FormGrid
、FormItem
、FormLayout
、Password
、Select
、自定义组件IDUpload
- 关联受控:作用域变量
$deps
、$self
,受控行为、路径查找 - 虚拟节点:充当
<Form.Item>
,充当交互组件,虚拟节点和对象节点不同处
通过Json Schema
创建注册:
- 通过
@emotion/styled
来设置组件样式,从而解决Json Schema
配置中无法使用antd-style
的Hooks来配置组件样式
通过JSX
创建注册:
Jsx
自增控件路径查找- 由于
ArrayItems
组件限定Schema
场景,这里由ArrayField
+ArrayBase
代替,包括代替方案存在的问题
通过Markup Schema
修改密码
通过Json Schema
修改密码:
Json Schema
中的key
和表单name
的关系
通过JSX
修改密码
---- 分割线 ----
- URL:
/edit-detail
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/EditDetail.tsx
- 包含章节:
包含:
通过组件PreviewText.Placeholder
实现预览:
- 默认全局预览方式
- 局部预览方式(下方查询列表[查看],个人补充了完整示例)
- 切换预览和编辑状态
- 表单组件根据可编辑状态受控响应(个人补充)
- 使用
useField
获取可编辑状态
巩固(个人补充):
- 无论组件怎么拆分,每个表单声明
createForm
只能匹配一个Form
- 无法使用
useField
以及无法通过受控获取可编辑状态时,可消费FormConsumer
获取实时状态
无法在
Filed
系列组件中通过component
或decorator
包裹的组件均无法受控、也不能使用useField
,这也包过除此之外的所有React
组件
---- 分割线 ----
- URL:
/table
- 目录:
- 包含章节:
包含:
由于查询列表文档只提供了一个概念以及一个参考性的代码,这里按照要求实践:
QueryList
:主要负责在顶层发请求QueryTable
:一个ArrayTable
,主要就是解析Schema
子树,自己拼装出Table
需要的Columns
数QueryForm
:负责查询筛选列表
为了实现这个需求,同时参考了两篇文档:
ArrayTable
、FormGrid
概括:
FormConsumer
消费数据更新改变提交状态ArrayTable
的使用方法及总结、ArrayTable.Column
的使用方法及总结SchemaField
内的组件使用与React
组件的不同- 通过
createForm
实现主动受控和被动受控 - 受控中使用
when
- 自定义组件
PriceInterval
,和之前的不同的是这里还自定义了组件的onChange
事件
---- 分割线 ----
- URL:
/dialog-drawer
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/DialogDrawer.tsx
- 包含章节:
包含:
演示了几个能力:
- 快速打开,关闭能力:通过
IFormDialog
对象触发open
和close
- 中间件能力:
forCancel
、forConfirm
、forOpen
(Drawer
仅支持forOpen
) - 渲染函数内可以响应式能力:
schema
和Field
- 上下文共享能力:仅限
Dialog
个人补充:
- 性能调优,通过拆分
schema
和Field
,具体见示例 Dialog
和Drawer
行为差异- 修复
Drawer
适配问题
修复: 截止于 24.03.18
FormDrawer
中使用antd v5
的Dom
结构和Api
已发生更改,影响包括:FormDrawer.Extra
和FormDrawer.Footer
无效;可以拷贝这个组件并替换组件路径:https://github.com/cgfeel/formily/blob/main/src/components/drawer/form-drawer/index.tsx
---- 分割线 ----
- URL:
/step-form
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/StepForm.tsx
- 包含章节:
包含:
FormStep.createFormStep
和createForm
一样,每次声明只能匹配一个form
- 在前面登录示例中演示了
createSchemaField
设置scope
,分步表单演示了后置动态设置scope
---- 分割线 ----
- URL:
/tab-collapse
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/TabCollapse.tsx
- 包含章节:
包含:
antd v5
对于选项卡、手风琴表单、自增选项卡、自增折叠表单 API 的调整,以及交互操作- 巩固:
Json Schema
中,对于需要为props
传递上下文的情况,可以通过scope
动态添加上下文对象 - 拆分
Markup Schema
组件 及Json Schema
修复:
截止于 24.03.20,@formily/antd-v5
部分组件 API 已废弃,我将其修复包括有:
FormTab
:不再支持TabPane
改为items
[查看]FormCollapse
:Api
已发生更改,不再支持CollapsePane
改为items
[查看]ArrayCollapse
:不再支持CollapsePane
改为items
[查看]
如果使用过程中官方仍旧没有修复,请查看对应的文件,拷贝修复
---- 分割线 ----
- URL:
/validate
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Validate.tsx
- 包含章节:
- 表单校验 [查看]
包含:
- 内置规则校验:
props
,x-validator
对象、x-validator
数组对象 - 内置格式校验:
props
,x-validator
对象(字符、对象、字符数组、对象数组) - 自定义规则校验:
registerValidateRules
- 自定义格式校验:
registerValidateFormats
- 异步校验:自定义规则校验 +
Promise
- 联动校验:
reactions
- 定义文案:
registerValidateLocale
和文档不同:
- 自定义规则校验,
Json Schema
是通过scope
这个props
动态添加局部定义规则;这样更符合实际应用场景 - 联动校验通过 3 种不同的方式进行:
createForm
中的effects
、schema
中的x-reactions
,Field
中的reactions
函数
---- 分割线 ----
- URL:
/layout
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Layout.tsx
- 包含章节:
包含:
Schema
中使用自定义非表单组件- 根据
antd v5.15.*
的 API 对组件进行修复 - 使用
antd v5
自带的css-in-js
添加组件样式 - 通过联动修改
FormItem
的布局 - 为
Schema
动态添加Component
组件修复:
Canscader
:将接口对应至antd v5
最新的 APIForm
:为FormLayout
提供上下文支持FormButtonGroup
:适配FormLayout
FormItem
:- 适配
FormLayout
,补充必选样式 - 修复
Select.multiple
、Switch
在布局尺寸变更时的适配
- 适配
FormLayout
:- 补充
layout: inline
布局支持,修复必选等接口 - 添加布局样式支持
- 补充
原本是想为
@formily/antd-v5
提交 PR,由于当前@formily/antd-v5
依赖的是antd v5.6
导致 API 不兼容,又不能修改工程依赖,所以采用这种方式进行修改
---- 分割线 ----
- URL:
/async
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/AsyncCom.tsx
- 包含章节:
包含:
Select
同步获取数据、异步搜索、异步联动的三种模式(下方注1)TreeSelect
同步获取数据、异步联动的三种模式Cascader
异步数据三种模式
注1:总结中,所有提到的三种模式分别为:
Markup Schema
、Json Schema
、Field Jsx
,如果没有单独说明,二种模式为:Markup Schema
、Json Schema
个人补充案例:
TreeSelect
异步加载数据三种模式Cascader
修复静态数据加载 [查看]- 适配
antd v5
最新版本中已废弃,而formily
中又必须传入的属性
- 适配
Cascader
异步加载数据三种模式- 对比异步数据不同的加载方式
异步数据加载方式:
- 初始只加载一次
- 依赖输入内容每次加载:
field.query({path}).value()
- 通过
observable.ref
创建引用劫持响应式对象 - 通过
scope
初始只加载一次 - 通过
scope
依赖输入内容每次加载
---- 分割线 ----
- URL:
/controlled
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Controlled.tsx
- 包含章节:
- 实现表单受控 [查看]
几个受控模式:
- 普通受控模式,随 React 组件生命周期,演示:只存在控制者、通过
props
受控、通过ref
受控 - 响应式值受控:使用
observable
提供数据用于响应受控 Schema
整体受控:通过替换整个form
和schema
实现表单整体切换Schema
片段联动:- 通过
form.clearFormGraph
回收字段模型,从而实现部分表单更新 - 通过
observer
包裹函数组件响应字段更新,常用于自定义组件
- 通过
包含:
- 修复文档普通受控模式的问题
- 最佳响应实践
- 反模式:和文档不同,这里分别从 3 个案例来演示受控逐步去掉响应的过程
- 补充
form.setValues
包含的 4 个类型的值的区别(文档并没有说明)
巩固:
- 在
React
组件中要响应表单数据,要么刷新整个表单;要么在受控组件上分别包裹observer
响应对应的表单数据变化 - 而在受控表单中 (
SchemaField
),则建议通过“响应式值受控”,这样不会多余消费React
组件性能 - 关于响应函数
observable
:创建响应值,提供给控制者或Form.values
,它包含一个observable.ref
,在“实现异步数据源”有提到observer
:响应组件的响应,用于在组件内容响应依赖数据的更新,暂且可以把它当作memo
来理解Observer
:用于在组件中提供一个响应区域,暂且可以把它当作useMemo
来理解
附加:
在这个演示中,包含 4 个 @formily/react
内容:RecursionField
、useForm
、useField
、observer
,分别在演示备注中说明,稍后在具体章节再演示
---- 分割线 ----
- URL:
/controlled
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/LinkAges.tsx
- 包含章节:
- 实现联动逻辑 [查看]
总结:
- 一对一通过:路径匹配 [查看]
- 一对多通过:通配符
*()
,如:*(.input1,input2)
- 依赖联动通过
field.query
或form.values
或schema
中使用dependencies
- 链式联动:依赖的字段可以层层匹配,例如:A 控制 B,B 控制 C,当 A 操作 B 的时候,C 也有可能受到响应变化,反之同理
- 循环联动:和依赖联动是一样的,区别在于依赖字段之间可以相互操作,谨慎使用,可能会导致逻辑问题
- 自身联动:将匹配的路径设为自身,或在被动联动中去掉路径
- 异步联动:在联动
hook
回调中,可以接受异步函数作为方法,作为异步联动,例如:fetch
包含:
- 以上7 个模式的
Effect
和Schema
方式,以及被动逻辑和联动逻辑分别演示
主动和被动联动:
onFieldValueChange
主动联动,onFieldReact
被动联动- 也可以在
schema
响应中使用reactions
,区别在于:主动联动有target
属性
因此:
- 在
schema
中,只有主动联动才需要指定生命周期effect
- 主动联动适合监听某一个固定字段,所以路径从监控的指定字段开始匹配
- 被动联动适合某一个字段受控于其他多个字段对齐的影响,所以路径从被监控的字段开始
附加:
FormEffectHooks
内容:FieldEffectHooks
、setFormState
、setFieldState
、SchemaReactions
、FieldReaction
,稍后在具体章节再演示,也可以启动演示项目查看相关备注
---- 分割线 ----
- URL:
/calculator
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Calculator.tsx
- 包含章节:
- 实现联动计算器 [查看]
包含:
联动逻辑运算 + 自增表单的一个练习,提供 Markup Schema
和 Json Schema
---- 分割线 ----
- URL:
/custom
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Custom.tsx
- 包含章节:
包含:
文档并没有提到具体内容,这里对前面所有内容归纳总结如下:
- 通过
props
转发给组件 - 定制
antd
组件 - 自定义非表单组件
- 修复
@formily/antd-v5
组件 - 通过
observer
包裹组件 - 在
observer
组件中使用hooks
- 通过
connect
接入组件库
---- 分割线 ----
数据解构 (前后端数据差异兼容方案):
- URL:
/destructor
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Destructor.tsx
将 name
由普通的字符修改为 [{name},{name}]
这样的方式解构字段
管理业务逻辑: 无案例,总结如下
两种方式:
- 全局设定:
form
中的effects
- 局部设定:
JSX
中使用reactions
或schema
中使用x-reactions
局部设定有缺点:
- 优点:简单、直接写在字段上
- 存疑:文档提到多场景,多字段维护,但
reactions
是可以和scope
结合使用的 - 缺点:不能逻辑复用,即便是
scope
结合使用的
全局设定有缺点:
- 优点:多字段处理,量化处理、逻辑复用、逻辑分离为单独文件
- 缺点:不能作为服务端配置
文档建议:
- 纯源码模式
- 字段数量庞大,逻辑复杂,优先选择
effects
中定义逻辑 - 字段数量少,逻辑简单,优先选择
reactions
中定义逻辑
- 字段数量庞大,逻辑复杂,优先选择
Schema
模式- 不存在异步逻辑,优先选择结构化
reactions
定义逻辑 - 存在异步逻辑,或者大量计算,优先选择函数态
reactions
定义逻辑
- 不存在异步逻辑,优先选择结构化
按需打包: 无案例,总结如下
antd v5
采用tree shaking
方式,无需使用文档中提到的babel-plugin-import
- 本项目是基于
create-react-app
,如果需要配置Webpack
还是建议添加这两个包:react-app-rewired
customize-cra
---- 分割线 ----
- URL:
/reactive
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Reactive.tsx
- 包含章节:
- 整个 API 文档 [查看]
总结:
- 暂且可以将其作为表单中的
mbox
来看,不同之处在于reactive
更注重于对响应对象的操作,其提供的 API 都围绕这方面 - 建议本地运行查看,这里只罗列索引,本地注释了详细备注
创建响应对象
observable/observable.deep
:深拷贝observable.shallow
:浅拷贝,响应对比observable.computed
:响应计算,直接计算,get\set
模式observable.ref
:引用响应劫持对象,只能用于修改对象的value
才能发生响应observable.box
:observable.ref
的get\set
模式
在演示中演示了执行过程的顺序
autorun
:接收一个函数函数作为tracker
,并返回一个dispose
函数用于停止响应autorun.memo
:在autorun
内部创建一个响应对象autorun.effect
:在autorun
内部的副作用处理
autorun
的执行过程通过微任务来实现
在演示中演示了执行过程的顺序
- 在响应过程中个执行一个脏检查,并返回一个
dispose
函数用于停止响应 - 方法中
tracker
会随着响应数据更新调用,而subscriber
只对更新数据有变化时才响应
定义批量操作:在同一个任务事件中拆分成不同的微任务,通过堆栈分别执行
batch.scope
:局部batchbatch.abound
:异步 batchbatch.endpoit
:结束回调
在演示中演示了:
batch
内部执行顺序batch
外部执行响应
batch
的执行过程通过微任务来实现
和 batch
一样,不同在于:
- 不收集依赖,关于依赖详细见演示
- 没有
endpoint
手动定义领域模型,在文档中有个问题:
- 在定义的模型类中
box
初始值是数值,之后通过define
修正为observable.box
- 然而
number
类型是没有get\set
操作的,这点对于vsc
、eslint
来说是不能理解的
修正:
- 直接将
box
类型通过observable.box
声明,取消define
类型覆盖
快速定义模型,详细见演示
和 autorun
的不同:
- 监听
observable
对象的所有操作,autorun
只响应值的变化 - 不响应
observable
初始值
两个特征
- 包裹对象,使其
observable
不响应 - 包裹类,使其类声明的对象都不受
observable
响应
observable
不响应有 3 类:React Node
与带有 toJSON
、toJS
方法的对象
两个特征:
- 包裹对象,使其
observable
响应 - 包裹类,使其类声明的对象受
observable
响应
从 observable
对象中获取源数据
将 observable
转化为普通的 JS 对象,转换后的值不能用于依赖收集
函数内包裹的 observable
永远不会被收集依赖
用于检测某段执行逻辑是否存在依赖收集,演示中分别鉴定:
toJS
属性对象、markObservable
对象、正常的observable
对象、markRaw
对象、toJS
对象- 只有
markObservable
对象、正常的observable
对象能够正常依赖
手动跟踪依赖,特征如下:
tracker.track
调用后不会重复执行Tracker
构造中的scheduler
声明后会随依赖值的变化而调用
借此每次跟踪结束后,需要通过 scheduler
来决定后续跟踪
isObservable
:判断是否为observable
对象,演示了observable
和observable toJS
isAnnotation
:判断是否为Annotation
对象,演示了action.bound
和普通函数isSupportObservable
:是否可以被observable
对象,演示了普通对象和带有toJS
的对象
在 React
中,将 Function Component
变成 Reaction
- 可以把
observer
和Observer
的关系看作是memo
和useMemo
- 演示中将文档示例做了拆分,建议对照比较
- 没有记录
vue
部分,当前以React
技术栈为主
更多往下查看单元测试
---- 分割线 ----
- URL:
/core
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/Core.tsx
- 包含章节:
- 整个 API 文档 [查看]
我将使用 formily
将这个库里对应的对象和属性,做成在线工具的形式进行演示,建议本地运行上手操作
创建一个 form
对象,其对象属性可以直接通过本地演示操作查看效果
表单生命周期
- 加载卸载:
onFormInit
、onFormMount
、onFormUnmount
- 表单变化:
onFormReact
、onFormValuesChange
、onFormInitialValuesChange
、onFormInputChange
- 表单提交:
onFormSubmitStart
、onFormSubmitSuccess
、onFormSubmitEnd
、onFormSubmitFailed
、onFormSubmit
- 提交验证:
onFormSubmitValidateStart
、onFormSubmitValidateSuccess
、onFormSubmitValidateFailed
、onFormSubmitValidateEnd
- 表单验证:
onFormValidateStart
、onFormValidateEnd
、onFormValidateSuccess
、onFormValidateFailed
生命周期顺序在本地演示说明,更多请本地运行查看:
- 加载卸载:
onFieldInit
、onFieldMount
、onFieldUnmount
- 字段变化:
onFieldReact
、onFieldChange
、onFieldValueChange
、onFieldInitialValueChange
、onFieldInputValueChange
- 字段验证:
onFieldValidateStart
、onFieldValidateEnd
、onFieldValidateFailed
、onFieldValidateSuccess
生命周期顺序在本地演示说明,更多请本地运行查看:
- 创建自定义钩子监听器:
createEffectHook
,详细见单元测试补充 - 创建一个副作用的上下文:
createEffectContext
,提供一个provide
,将组件中的能力托管出来,提供一个消费方consume
在外部消费上下文托管对象 - 在表单生命周期中获取当前
form
对象:useEffectForm
,可以搭配createEffectContext
一起使用
- 对象检查:
isForm
、isField
、isArrayField
、isObjectField
、isVoidField
、isGeneralField
、isDataField
、isQuery
- 状态检查:
isFormState
、isFieldState
、isArrayFieldState
、isObjectFieldState
、isVoidFieldState
、isGeneralFieldState
、isDataFieldState
本地使用 formily
开发演示工具,更多请本地运行查看:
本地使用 formily
开发演示工具
- 包含:属性、数据路径语法、匹配路径语法、方法、静态方法进
- 支持自定义路径匹配查询结果
更多请本地运行查看:
formily
验证格式、定制信息、定制模板、校验规则、语言等,包含:
setValidateLanguage
:定制语言registerValidateFormats
:注册校验格式,包含全局注册、局部注册registerValidateLocale
:定制提示registerValidateMessageTemplateEngine
:定制信息模板registerValidateRules
:定制校验规则,包含全局定制、局部定制getValidateLocaleIOSCode
:获取内置存在的ISO Code
更多往下查看单元测试
---- 分割线 ----
- URL:
/react
- 目录:https://github.com/cgfeel/formily/blob/main/src/page/ReactLibrary.tsx
- 包含章节:
- 整个 API 文档 [查看]
详细了解建议:查看本地演示或查看单元测试
Field
:普通字段ArrayField
:数组字段ObjectField
:对象字段VoidField
:虚拟字段SchemaField
:解析Schema
、渲染字段、提供scope
、传递上下文RecursionField
:递归渲染组件,主要有 2 种,将属性递归,组件自身递归。详细见单元测试FormProvider
:入口组件,传递上下文FormConsumer
:在SchemaField
外部消费表单ExpressionScope
:自定义组件内部给json-schema
表达式传递局部作用域RecordScope
:作用域注入组件一个有层级对象,主要三个变量:$record
、$index
、$lookup
RecordsScope
:作用域注入组件一个对象集合,主要三个变量:$records
useExpressionScope
:自定义组件中读取表达式作用域useField
:自定义组件内读取当前字段属性,操作字段状态等useFieldSchema
:自定义组件中读取当前字段的Schema
信息useForm
:自定义组件中读取当前Form
实例useFormEffect
:自定义组件中往当前Form
实例额外注入副作用逻辑userParentForm
:读取最近的Form
或者ObjectField
实例
本地演示补充:
useExpressionScope
包含各个层级作用域下发捕获useField
和useFieldSchema
区别userParentForm
补充了父子表单交互示例
connect
:第三方组件库的无侵入接入Formily
mapProps
:将Field
属性与组件属性映射的适配器函数,主要与connect
函数搭配使用mapReadPretty
:给组件赋予阅读状态,主要与connect
函数搭配使用observer
:为react
函数组件添加reactive
特性
需要通过单元测试了解:
以下建议通过单元测试、formily
组件源码了解使用
FormContext
:Form
上下文,可以获取当前Form
实例FieldContext
:字段上下文,可以获取当前字段实例SchemaMarkupContext
:Schema
标签上下文SchemaContext
:字段Schema
上下文SchemaExpressionScopeContext
:Schema
表达式作用域上下文SchemaOptionsContext
:Schema
全局参数上下文,主要用于获取从createSchemaField
传入的参数Schema
:解析、转换、编译json-schema
的能力
从
@formily/react
中可以导出Schema
这个Class
,但是不希望使用@formily/react
,可以单独依赖@formily/json-schema
这个包
更多往下查看单元测试
---- 分割线 ----
内部方法不再当前研究范围,不包含测试用例:array.spec.ts
[查看]
不可收集依赖的批量操作,具体特性见单元测试
action
批量操作普通用法:
- 不使用
action
每次修改observable
都会响应一次 action
内部所有修改只记录一次响应- 在
track
函数中使用action
action.bound
绑定一个批量操作- 在
track
函数中使用action.bound
action.scope
在action
中分批执行- 使用
action.socpe.bound
- 在
track
函数中使用action.scope
- 在
track
函数中使用action.scope.bound
define
定义模型中使用 action
批量操作:
define
中使用action
- 在
track
函数中使用模型action
define
中使用action.bound
track
函数中使用模型action.bound
define
中使用action.scope
define
中使用action.scope.bound
track
函数中使用模型action.scope
track
函数中使用模型action.scope.bound
- 嵌套
action
批量操作在reaction
中subscrible
- 嵌套
action
和batch
批量操作在reaction
中subscrible
定义批量操作,内部可以收集依赖
batch
批量操作普通用法:
- 不使用
batch
每次修改observable
都会响应一次 batch
内部所有修改只记录一次响应- 在
track
函数中使用batch
batch.bound
绑定一个批量操作- 在
track
函数中使用batch.bound
batch.scope
在batch
中分批执行- 使用
batch.socpe.bound
- 在
track
函数中使用batch.scope
- 在
track
函数中使用batch.socpe.bound
- 在
batch
中抛出错误
define
定义模型中使用 batch
批量操作:
define
中使用batch
- 在
track
函数中使用模型batch
define
中使用batch.bound
track
函数中使用模型batch.bound
define
中使用batch.scope
define
中使用batch.socpe.bound
- 在
track
函数中使用模型batch.scope
- 在
track
函数中使用batch.socpe.bound
批量操作结束回调,batch
独有 action
没有:
batch.endpoint
注册批量执行结束回调batch.endpoint
意外的结束 - 不提供回调也不会执行- 直接使用
batch.endpoint
batch.endpoint
不是微任务,而是单纯的回调函数
其他:
- 在
reaction
有效的收集依赖触发subscrible
- 在
reaction subscript
中无效的依赖不会反向触发响应
参考:
reaction
中subscrible
不收集依赖 [查看]
- 正常的情况:
track
函数会收集依赖,其他中两个例子是通过autorun
收集依赖触发reaction
的track
函数中的依赖进行响应 - 不正常的情况:在
reaction
的subscrible
函数中会随着track
函数响应触发调用,但在subscrible
添加observable
对象,试图更新反向触发响应是行不通的
创建不同响应式行为的 observable
对象:
observable
创建劫持对象 - 默认深度劫持observable.shallow
创建的是浅劫持响应式对象observable.box
创建引用劫持响应式对象,带有get
/set
方法observable.ref
创建引用劫持响应式对象action.bound
中更新observable
对象- 非批量操作中更新
observable
对象 observable.computed
创建一个计算缓存器- 创建一个链式
observable.computed
model
快速定义领域模型model
中创建一个计算缓存器,计算数组长度model
中创建一个计算缓存器,收集依赖observable.computed
容错机制observable.computed
接受一个带有 get 属性方法的对象untracked
中使用observable.computed
对象define
定义一个类为领域模型
劫持 Map
类型对象作为 observable
对象:
- 创建一个
Map
类型的observable
对象 - 在
autorun
中响应map
类型对象 - 在
autorun
中响应map.size
- 在
autorun
中通过for of
迭代map
获取值 - 在
autorun
中通过forEach
迭代map
获取值 - 在
autorun
中通过for of
迭代map.keys
获取值 - 在
autorun
中通过for of
迭代map.values
获取值 - 在
autorun
中通过for of
迭代map.entries
获取值 - 在
autorun
中通过map.clear
触发响应 - 在
autorun
中不响应错误的map
自定义属性 - 在
autorun
中不响应未更新的数据 - 在
autorun
中不响应map
类型原始数据 - 在
autorun
中不响应map
类型原始数据迭代 - 在
autorun
中不响应map
类型原始数据的增删操作 - 在
autorun
中不响应map
类型原始数据长度 - 在
autorun
中不响应map
类型原始数据添加 map
类型的observable
对象中允许使用object
作为key
map
类型的observable
对象中允许设置一个普通对象,或是observable
对象作为value
- 浅劫持
map
对象,不会响应对象的属性值修改
劫持 Set
类型对象作为 observable
对象
- 创建一个
set
类型的observable
对象 - 在
autorun
中响应set
类型对象 - 在
autorun
中响应set.size
- 在
autorun
中通过for of
迭代set
- 在
autorun
中通过forEach
迭代set
- 在
autorun
中通过for of
迭代set.keys
获取值 - 在
autorun
中通过for of
迭代set.values
获取值 - 在
autorun
中通过for of
迭代set.entries
获取值 - 在
autorun
中不响应set
错误的自定义属性 - 在
autorun
中不响应没有变更的set
对象 - 在
autorun
中不响应set
类型原生数据迭代 - 在
autorun
中不响应set
类型原生数据的新增、删除 - 在
autorun
中不响应set
类型原生数据的set.size
- 在
autorun
中不响应来自set
类型原生数据添加的项目
劫持 WeakMap
类型对象作为 observable
对象
- 创建一个
WeakMap
类型的observable
对象 - 在
autorun
中响应WeakMap
类型对象 - 在
autorun
中不响应WeakMap
不合理的自定义属性更新 - 在
autorun
中不响应WeakMap
中没有更新的属性 - 在
autorun
中不响应WeakMap
原生对象 - 在
autorun
中不响应来自WeakMap
原生对象增删操作
劫持 WeakSet
类型对象作为 observable
对象
- 创建一个
WeakSet
类型的observable
对象 - 在
autorun
中响应WeakSet
对象 - 在
autorun
中不响应WeakSet
对象不合理的自定义属性更新 - 在
autorun
中不响应WeakSet
对象没有更新的属性 - 在
autorun
中不响应WeakSet
原生对象 - 在
autorun
中不响应来自WeakSet
原生对象触发的增删操作
-
目录:https://github.com/cgfeel/formily/blob/main/src/__tests__/reactive/observable.spec.ts
-
array observable
操作 -
contains
判断observable
对象中是否包含指定对象 -
不能直接设置
observable
的__proto__
手动定义领域模型
define
+observable
,响应手动定义领域模型define
+observable.shallow
,响应手动定义浅劫持领域模型define
+observable.box
,响应手动定义一个带有get
/set
方法的领域模型define
+observable.ref
,响应手动定义一个领域模型的引用define
+observable
+batch
,批量操作中响应手动定义的领域模型define
+observable.computed
,响应手动定义一个领域模型的计算缓存器define
手动定义领域模型容错机制model
快速定义领域模型
model
是快速定义一个模型
getter
/setter
属性自动声明computed
- 函数自动声明
action
- 普通属性自动声明
observable
define
需要手动定义对象,可以是对象,也可以是一个类,需要手动指定对象属性、方法作为 observable
接收一个 tracker
函数用于响应 observable
数据变化,他们都返回一个 dispose
函数用于停止响应。分别如下:
autorun
:只接受tracker
函数reaction
:- 接受
tracker
函数 - 接受
callback
函数作为subscrible
- 接受一个属性用于初始化执行、脏检查等
- 接受
除此之外在 tracker
函数中会自动收集 observable
依赖,除非:
- 使用了非
observable
对象 - 使用的对象被
action
、untracked
包裹
注意:
- 浅响应只响应指定对象,对象下的属性除非指定情况下会收集,否则不会主动收集
reaction
可以通过equals
脏检查将对象通过JSON.stringify
转换成字符串作为深比较,但建议深比较通过下方的observe
来响应
测试示例:
- 创建一个
autorun
- 创建一个
reaction
,监听subscrible
订阅 reaction
初始化后立即响应reaction
中subscrible
不收集依赖reaction
中进行数据的脏检查reaction
响应中浅比较 - 默认reaction
响应中深比较- 在
autorun
中递增observable
对象 autorun
初始化收集的依赖决定后续响应情况autorun
中间接递归响应 - 单向递归autorun
中间接递归响应 - 批量操作递归autorun
跳出响应前,通过头部赋值收集依赖autorun.memo
在autorun
中用于创建持久引用数据- 使用
observable
对象创建一个autorun.memo
autorun.effect
在autorun
添加一个微任务,在响应结束前执行autorun.memo
中添加依赖autorun.memo
响应依赖更新以及autorun
停止响应autorun.memo
容错,传递无效值- 在
autorun
外部使用autorun.memo
会抛出错误 - 在
autorun
中不使用autorun.memo
无效递增 autorun.effect
微任务的执行和结束回调autorun.effect
结束前autorun
已dispose
autorun.effect
添加依赖autorun.effect
不添加依赖默认为[{}]
,随autorun
每次都响应autorun.effect
在autorun
外部使用将抛出错误autorun.effect
容错- 在
batch
内部停止autorun
响应 autorun
依赖observable.computed
计算的值autorun
依赖observable.computed
对象在delete
后的响应autorun
依赖observable.computed
使用Set
类型对象autorun
依赖observable.computed
删除Set
类型子集autorun
依赖observable.computed
使用Map
类型对象autorun
依赖observable.computed
删除Map
类型子集autorun
中有条件的依赖收集reaction
中有条件的依赖收集、subscrible
、fireImmediately
在依赖发生变化时不会重复执行 tracker
函数,需要用户手动重复执行,只会触发 scheduler
Tracker
手动跟踪基础用法Tracker
嵌套跟踪Tracker
根据条件收集依赖Tracker
共享跟踪调度器
监听 observable
对象的所有操作,支持深度监听也支持浅监听
observe
深响应observe
浅响应 - 第三个参数设置false
observe
响应根节点替换observe
通过dispose
停止响应observe
中track
函数的使用给定的参数进行条件判断observe
中动态树,见注 ②observe
响应对象传递为函数将会抛错
注 ①:
当收集
observable
对象作为依赖进行响应的时候,修改的值没有变化,那么不会触发响应,这仅限于对象的值是原始类型数据;如果对象的值是引用类型的数据,由于引用的地址发生了改变,即便看上去值是一样的,依旧会触发响应,除非更新的对象也是同一个引用地址的值
注 ②:
observable
对象树中动态添加的observable
节点,会响应深度修改observable
对象树中静态存在的observable
节点,只响应浅度修改
详细见单元测试代码
isSupportObservable
:
判断可以作为 observable
对象的类型,可以作为 observable
对象的类型:
observable
对象- 不在排除范围的对象:类声明对象、普通对象、
Array
、Map
、WeakMap
、Set
、WeakSet
不可以作为响应劫持对象的类型:
null
、React Node
、MomentJS
对象、JSON Schema
、带有toJS
/toJSON
方法的对象
isObservable
:
判断对象是否为 observable
对象
makeRaw
:
- 创建一个永远不可以作为
observable
的对象 - 标记一个类,使其声明的对象永远不可作为
observable
markObservable
:
- 将一个带有
toJS
方法的对象作为observable
markObservable
只能接受一个对象作为observable
,不能将函数转换为observable
makeRaw
的权重比markObservable
高,无论是makeRaw
包裹markObservable
,还是markObservable
包裹makeRaw
都不能够作为observable
对象
补充:
- 递归
observable
并打印JS
检测某段执行逻辑是否存在依赖收集
untracker
函数内部永远不会被依赖收集,和 action
一样,不同的是 untracker
不支持批量操作
untracker
函数内部永远不会被依赖收集untracker
不提供参数什么也不会发生
---- 分割线 ----
内部方法不再当前研究范围,不包含测试用例:
普通字段
- 创建字段:
createField
- 创建带有属性的字段
- 字段值和展示状态:
display
、value
- 嵌套展示和表单模式:
display
、pattern
- 设置字段值、初始值:
setValue
、setInitialValues
- 设置字段加载状态和验证状态:
setLoading
、setValidating
- 设置字段组件和组件属性:
setComponent
、setComponentProps
- 设置装饰组件和装饰组件的属性:
setDecorator
、setDecoratorProps
- 响应式初始值:
reactions
+field.initialValue
- 字段验证状态、错误、警告、成功、有效、无效、验证状态、反馈信息:
selfValidate
、errors
、warnings
、successes
、valid
、invalid
、validateStatus
、queryFeedbacks
- 设置验证器:
setValidatorRule
- 字段查询:
query
- 初始值为空:
initialValue: ""
- 有初始值的对象字段:
initialValue: { obj: {} }
- 有初始值的数组对象:
initialValue: [1, 2]
- 重置对象字段的初始值:
form.reset
- 字段重置:
field.reset
- 匹配路径:
field.match
- 获取、设置字段状态:
setState
、getState
- 设置字段数据源:
setDataSource
- 设置字段标题和介绍:
setTitle
、setDescription
- 必填字段,设置必填:
required
、setRequired
- 设置字段值的 data 和 content:
setData
、setContent
- 设置虚拟字段的 data 和 content:
setData
、setContent
- 设置字段验证状态:
setErrors
、setWarnings
、setSuccesss
、setValidator
- 字段联动:
reactions
- 字段容错
- 初始值:
initialValue
- 无索引数组路径计算,下标 0 创建字段
0.input
:[{ input: "123" }]
- 无索引嵌套虚拟节点的数组路径计算,下标 0 创建虚拟节点
column
,可以直接忽略,直接查找虚拟节点下的input
:[{ input: "123" }]
- 通过对象索引计算数组路径,下标 0 是对象节点,对象节点下的
input
:[{ input: "123" }]
- 通过虚拟索引计算数组路径,下标 0 作为虚拟节点,节点下包含字段:
["123"]
- 外层包裹一个虚拟节点并通过虚拟索引计算数组路径,可忽略外最层
- 在联动中收集依赖触发响应:
reaction
+field.query
- 嵌套字段隐藏和验证:
display
、field.validate
- 深度嵌套字段隐藏和验证:
display
、form.validate
- 深度嵌套字段隐藏和通过中间字段隐藏自身验证状态
- 字段卸载和验证状态:
field.onUnmount
、field.validate
- 数组字段下的自动清除:
form.setValues({ array: [] }
- 对象字段下的自动清除:
obj1.setValue({})
,和数组字段不一样,建议看单元测试 - 初始值为空的字段:
initialValue: ""|null
- 字段提交:
field.submit
- 带有错误的字段提交
- 初始值和展示状态的关系:
initialValue
、visible
- 字段受控展示状态:
reactions
+visible
- 字段值和初始值受控:
reactions
+initialValue
- 字段名叫
length
(JS
保护名称) 的初始值 - 字段名叫
length
,动态分配初始值 - 嵌套字段的修改:
field.modified
、field.selfModified
- 连续验证字段:
field.setValidator([validator1, validator2])
- 在自定义验证器中获取上下文字段和表单:
validator(_, __, _ctx) {}
- 单方向联动:
reactions
- 修改字段路径会重新计算字段值:
field.locate
- 重置对象字段:
form.reset
- 字段展示状态决定默认值是否有效:
onFieldReact
+field.visible
- 通过相邻路径查找值:
.{path}
- 相对路径查找虚拟节点下的字段
- 表单值和字段值定义和覆盖:
initialValue
、value
- 销毁字段同时销毁值:
field.destroy
- 字段校验只校验第一个非法规则:
validateFirst: true
- 注销字段不再响应联动:
field.destroy
+reactions
- 父级设置
readPretty
会覆盖disabled
、readOnly
的子集pattern
- 字段验证错误,不影响其他字段
- 字段注销后不再合并到表单值中:
field.destroy
onInput
通过target
传值:field.onInput({ target })
- 表单初始值忽略已注销的字段、隐藏的字段:
display: none
、field.destroy
- 字段
actions
、字段方法注入inject
、调用invoke
- 字段隐藏保留值
display: hidden
和不保留值display: none
- 解构字段的展示状态:
{ name: [aa,bb] }
onInput
修改规则:field.onInput({ currentTarget, target });
- 通过聚焦和失焦对无效的目标值触发验证:
validator: [{ triggerType }]
数组字段:
- 创建数组字段:
createArrayField
- 数组字段方法:
push
、pop
、unshift
、remove
、insert
、move
、shift
、moveDown
、moveUp
- 数组字段下标操作和交换:
push
、pop
、unshift
、remove
、insert
、move
- 数组下标字段移动:
move
- 查询数组字段下标路径:
form.query
、form.fields
- 数组字段中的虚拟子节点:
createVoidField
+basePath
- 交换数组字段的子集:
move
- 数组字段容错
- 修改数组字段容错
- 数组字段通过
move
API 移动子集 - 数组添加、删除、创建子字段触发回调:
onFormValuesChange
、onFormInitialValuesChange
、onFieldValueChange
- 嵌套字段的
indexes
,以及删除数组下的节点的坑点:field.indexs
- 数组字段的
indexes
需要避免无效的数字:field.index
- 在数组字段中没有字段的节点:
unshift({})
- 数组节点中可以跳过虚拟节点,直接获取数据
- 数组字段清空:
form.reset("*")
、form.reset("*", { forceClear: true })
- 数组字段删除节点不会导致内存泄露:
remove
+onFieldValueChange
- 数组字段修补值:
unshift
- 数组字段初始值通过
remove
删除 - 从
records
中查找数组字段 - 在数组嵌套字段中查找
record
- 查找数组字段中的
record
- 获取对象字段的
record
- 获取表单的
record
对象字段:
- 创建
ObjectField
对象:createObjectField
ObjectField
对象的方法:field.addProperty
、field.removeProperty
、field.existProperty
虚拟字段:
- 创建虚拟节点,注销虚拟节点:
createVoidField
、field.destroy
- 创建带有属性的虚拟节点
- 设置组件和组件属性:
field.setComponent
、field.setComponentProps
- 设置标题、描述:
setTitle
、setDescription
- 设置包装器和包装器属性:
setDecorator
、setDecoratorProps
- 设置、获取状态:
setState
、getState
- 嵌套展示、字段模式:
setPattern
、setDisplay
- 联动:
reactions
- 容错机制
- 子集联动:
reactions
+field.query
调用 createForm
所返回的核心表单模型:
- 创建
form
对象并挂载:createForm
- 创建字段:
createField
、createArrayField
、createObjectField
、createVoidField
- 设置值、初始值:
setValues
、setInitialValues
- 没有值的字段会使用初始值合并:
initialValue
、value
- 设置表单加载状态:
setLoading
- 允许设置值为
null
- 允许设置表单值为
observable
对象 - 删除表单值,初始值:
deleteValuesIn
、deleteInitialValuesIn
- 表单提交、验证:
setSubmitting
、setValidating
- 添加、删除、覆盖式更新副作用:
addEffects
、removeEffects
、setEffects
- 字段查询:
query
- 通知、订阅、取消订阅:
notify
、subscribe
、unsubscribe
- 设置和获取表单状态、字段状态:
setState
、getState
、setFormState
、getFormState
、setFieldState
、getFieldState
- 表单验证:
validate
、valid
、invalid
、errors
、warnings
、successes
、clearErrors
、clearWarnings
、clearSuccess
、queryFeedbacks
- 表单模式:
setPattern
、pattern
、editable
、readOnly
、disabled
、readPretty
- 表单展示状态:
setDisplay
、display
、visible
、hidden
- 表单提交:
form.submit
- 表单重置:
form.reset
- 测试表单在调试工具下的表现,检查
__FORMILY_DEV_TOOLS_HOOK__
对象 - 重置数组字段:
form.reset
- 重置对象字段:
form.reset
- 创建字段前初始值合并表单值,注 ①
- 不能匹配的字段为空值
- 创建字段后合并初始值,和注 ① 一样,不同的是赋值的顺序
- 删除表单中没有定义的值:
display
+hasOwnProperty
- 初始值为空数组
- 表单生命周期触发回调:
onFormInitialValuesChange
、onFormValuesChange
- 修改表单中的数组字段默认值:
push
+onFormValuesChange
- 深度合并值:
form.setValues
validator
中throw new Error
- 可重复声明表单字段并覆盖字段值:
designable: true
- 跳过验证
display: none
的字段 - 跳过验证已卸载:
aa.onUnmount
不能跳过,需要回收字段:form.clearFormGraph
- 跳过验证不可编辑的字段:
field.editable = false
- 带有格式的验证命令:
validator: { format }
、validator(value) {}
- 卸载表单不会影响字段值:
form.onMount
- 回收字段会清除字段值:
form.clearFormGraph("*")
- 回收字段不清除字段值:
form.clearFormGraph("*", false)
- 表单自动回收不可见的字段值:
reactions
+visible
- 通过异步设置初始值,自动隐藏表单不可见的字段值:
reactions
+visible
、form.setInitialValues
- 表单值不会因为
setValues
改变,注 ② - 表单初始值不会因为
setInitialValues
改变,注 ② - 表单字段设为
undefined
不会报错,会被表单忽略:form.fields['a'] = undefined;
注 ②,在表单字段创建前不能通过
setValue
或setInitialValue
赋值,但是可以通过表单直接赋值form.value = {}
或form.initialValue = {}
,字段创建后不再受到限制
表单、字段生命周期:
- 表单初始化、挂载、卸载监听:
onFormInit
、OnFormMount
、onFormUnmount
- 监听表单值改变、表单初始值改变:
onFormValueChange
、onFormInitialValueChange
- 监听表单输入:
onFormInputChange
- 表单响应式逻辑:
onFormReact
- 重置表单:
onFormReset
- 表单提交:
onFormSubmit
- 表单验证:
onFormValidate
- 字段主动受控:
onFieldChange
- 字段初始化、字段挂载、字段卸载:
onFieldInit
、onFieldMount
、onFieldUnmout
- 字段初始值变更、字段值变更、字段输入值变更:
onFieldInitialValueChange
、onFieldValueChange
、onFieldInputValueChange
- 字段被动受控:
onFieldReact
- 字段验证:
onFieldValidate
- 副作用里异步监听生命周期会抛出错误
createEffectContext
副作用上下文form.lifecycles
表单副作用集合
备注:无论是表单提交还是表单验证,它们都是微任务
检查对象类型:
- 检查字段:
isField
、isArrayField
、isObjectField
、isVoidField
、isDataField
、isGeneralField
- 检查字段状态:
isFieldState
、isArrayFieldState
、isObjectFieldState
、isVoidFieldState
、isDataFieldState
、isGeneralFieldState
- 检查表单:
isForm
、isFormState
、isQuery
自定义 effect
:
createEffectHook
字段模型操作方法
- 回收字段模型:
form.clearFormGraph
- 获取字段模型:
form.getFormGraph
- 设置字段模型:
form.setFormGraph
---- 分割线 ----
自定义组件内部给 json-schema
表达式传递局部作用域:
- 创建带有
ExpressionScope
组件的表单 - 忽略编译
{{variable}}
- 表单隐藏和可见:
form.fields[{name}].hidden
、form.fields[{name}].visible
createField
的 React
实现:
- 没有
FormProvider
上下文渲染Field
会抛出错误' - 渲染字段:
Field
、ArrayField
、ObjectField
、VoidField
- 字段属性基础操作:
mounted
- 数组字段属性:
moveDown
、mounted
、value
- 注入副作用逻辑:
observer
+useFormEffects
+onFieldChange
- 第三方组件库的无侵入接入
Formily
:connect
+mapProps
+mapReadPretty
- 字段验证和卸载:
onFieldUnmount
、form.validate
不包含:
ReactiveField
:这个字段不对外导出,可以通过ArrayField
进行了解
表单渲染相关:
- 渲染表单:
FormProvider
、FormConsumer
- 查询最近的
ObjectField
或Form
:useParentForm
使用 JsonSchema
渲染:
- 通过
JsonSchema
渲染单一字段 - 通过
JsonSchema
渲染带有对象字段的表单 - 通过
JsonSchema
渲染单一对象字段 - 通过
x-component-props
传递children
- 通过
x-content
传递children
使用 MarkupSchema
渲染节点:
- 字符节点:
SchemaField.String
- 布尔节点:
SchemaField.Boolean
- 数值节点:
SchemaField.Number
- 日期节点:
SchemaField.Date
- 日期时间节点:
SchemaField.DateTime
- 虚拟节点:
SchemaField.Void
- 数组节点:
SchemaField.Array
- 对象节点:
SchemaField.Object
- 其他节点:
SchemaField.Markup
- 虚拟节点支持
children props
,SchemaField.Markup
不支持children
- 子节点 - 通过
props
设置children
- 通过
x-content
设置children
RecursionField
递归渲染组件:
onlyRenderProperties
:只渲染schema properties
mapProperties
:schema properties
映射器,主要用于改写schema
filterProperties
:schema properties
过滤器,被过滤掉的schema
节点不会被渲染onlyRenderSelf
:是否只渲染自身,不渲染properties
,注 ①- 引入一个无效的
schema
schema
联动:x-reactions
- 作用域范围:
scope
x-content
通过scope
将组件作为children
- 作用域内隐藏和展示:
x-visible
- 作用域联动设置值:
x-value
- 通过作用域嵌套更新组件属性:
x-component-props
- 通过联动嵌套更新组件属性:
x-reactions
schema
验证和必填:x-validator
、required
schema
根据值响应:{{$values.input}}
- 虚拟节点的
children
- 在响应执行过程中,调用第三方字段的值
- 触发更新:
onClick
+onChange
x-reactions
响应:target
+onFieldInputValueChange
- 触发更新:
- 多个响应的副作用隔离
- 触发更新:
onClick
+onChange
x-reactions
响应:dependencies
+{{$deps}}
- 触发更新:
- 嵌套作用域记录对象:
RecordScope
- 作用域记录对象字面量:
{{$record + $index}}
- 作用域集合:
RecordsScope
propsRecursion
:RecursionField
的proprety
是否递归传递mapProperties
和filterProperties
- 不提供
propsRecursion
过滤schema
只能过滤下一级字段,对于更深层次的字段不能过滤
注 ①:
ObjectField
下所有子集都是properties
---- 分割线 ----
包含有 4 篇文档:
Formily
的Reactive
的经验汇总 [查看]Formily
的core
的经验汇总 [查看]Formily
的React
库经验汇总 [查看]Formily
的Antd
经验汇总 [查看]
如果是没有接触过
Formily
的新手,建议从上面官方文档示例开始看;对于这几篇文档提到的Formily
知识点有限,且存在有错误,我会在演示中纠正部分错误;而对于已经了解Formily
,这几篇文档可以作为补充练习来看
这里我将 4 篇文章比较精彩的部分罗列出来,其他的大多和官方演示一样,可以直接本地运行查看
包含以下章节:
core
.0: 仅用@formily/reactive
实现表单逻辑 [查看]core
.10: 为之前的reactive
表单增加core
[查看]React
.3: 复现Field
[查看]React
.4: 复现Schema
[查看]
原理直接本地运行查看演示备注,除此之外还可以看看字段和模型的实践
Reactive
.5:多计数器 [查看]Core
.1.1:字段值的获取与更新 [查看]Core
.2 展示状态 [查看]Core
.3 校验和反馈 [查看]React
.7.1:value
与onChange
的隐式传递 [查看]
---- 分割线 ----
原文章:手把手教你写一个条件组合组件 [查看],将其用 Formily
复现
目录:https://github.com/cgfeel/formily/tree/main/src/components/objectBase
优点:
- 简洁,就 2 文件
- 一套模型,不用修改模型代码1 个字母,就可拓展 N 个逻辑渲染,如下图
---- 分割线 ----
在刷 Formily
的时候时常会有个疑问,Formily
的优势是什么,对比诸如:react-hook-form
、zod
,亦或者直接使用 Antd
,我有什么非用 Formily
不可的地方吗?在刷完整个文档后我大致总结出这么几条:
- 低代码:
Formily
天生为低代码生的 - 赋予
Schema
模型处理的能力 DMS
数据管理服务
几乎市面上所有的表单验证库也好,还是相关的库也好,它们都是围绕:验证、受控展开,但是很少会提到:模型渲染、数据分离,这应该就是 Formily
特有的地方了,它不仅仅是一个表单验证、受控的库。借助模型处理的能力,可以无限拓展、分离、组合不同的 schema
,实现、管理一个极其庞大的数据。
当然带来的问题就是学习成本的上升,要了解 Formily
,就要了解一套状态管理 Reactive
、了解副作用及声明周期和相关对象 Core
、了解一套现有的渲染引擎库 React
,以及对应的一套主题,如:Antd
那什么时候选择使用 Formily
呢?
- 看使用场景,比如做低代码,诸如:问卷调查、任务引擎等
- 看业务处理
- 如果你仅仅需要处理注册、登录、商品购买提交订单,
Formily
可能并非最优解 - 如果你需要做比较大的数据处理可以考虑
Formily
,例如:商城平台订单管理,分大类目、小类目、不同 SKU,库存,价格,优惠策略等等 - 亦或者你需要做一个 CRM 可以考虑
Formily
,管理不同的客户群体,付费情况、不同链路渠道划分、统计等等 - 一个复杂的动态结果页,比如你有一个上千模型的落地页,根据不同的情况,按照不同的状态、不同的顺序分别展示
- 如果你仅仅需要处理注册、登录、商品购买提交订单,