按照
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
所有示例根据自己理解添加了说明、并根据自己的理解补充示例;在文档最后补充两个作业:
formily
总结做一套练习
主要的使用的包:
v18.13.0
Create React App
+
react-app-rewired
+
customize-cra
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
将会每个演示包含的注解,作为索引简要的罗列出来,方便查找
/start
/login
通过
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
修改密码
---- 分割线 ----
/edit-detail
PreviewText
[
查看
]
通过组件
PreviewText.Placeholder
实现预览:
useField
获取可编辑状态
巩固(个人补充):
createForm
只能匹配一个
Form
useField
以及无法通过受控获取可编辑状态时,可消费
FormConsumer
获取实时状态
无法在
Filed
系列组件中通过
component
或
decorator
包裹的组件均无法受控、也不能使用
useField
,这也包过除此之外的所有
React
组件
---- 分割线 ----
/table
ArrayTable
[
查看
]
FormGrid
[
查看
]
由于查询列表文档只提供了一个概念以及一个参考性的代码,这里按照要求实践:
QueryList
:主要负责在顶层发请求
QueryTable
:一个
ArrayTable
,主要就是解析
Schema
子树,自己拼装出
Table
需要的
Columns
数
QueryForm
:负责查询筛选列表
为了实现这个需求,同时参考了两篇文档:
ArrayTable
、
FormGrid
FormConsumer
消费数据更新改变提交状态
ArrayTable
的使用方法及总结、
ArrayTable.Column
的使用方法及总结
SchemaField
内的组件使用与
React
组件的不同
createForm
实现主动受控和被动受控
when
PriceInterval
,和之前的不同的是这里还自定义了组件的
onChange
事件
---- 分割线 ----
/dialog-drawer
FormDialog
[
查看
]
FormDrawer
[
查看
]
演示了几个能力:
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
---- 分割线 ----
/step-form
FormStep
[
查看
]
FormStep.createFormStep
和
createForm
一样,每次声明只能匹配一个
form
createSchemaField
设置
scope
,分步表单演示了后置动态设置
scope
---- 分割线 ----
/tab-collapse
FormCollapse
[
查看
]
FormTab
[
查看
]
ArrayCollapse
[
查看
]
ArrayTabs
[
查看
]
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
[
查看
]
如果使用过程中官方仍旧没有修复,请查看对应的文件,拷贝修复
---- 分割线 ----
/validate
props
,
x-validator
对象、
x-validator
数组对象
props
,
x-validator
对象(字符、对象、字符数组、对象数组)
registerValidateRules
registerValidateFormats
Promise
reactions
registerValidateLocale
和文档不同:
Json Schema
是通过
scope
这个
props
动态添加局部定义规则;这样更符合实际应用场景
createForm
中的
effects
、
schema
中的
x-reactions
,
Field
中的
reactions
函数
---- 分割线 ----
/layout
FormLayout
[
查看
]
FormItem
[
查看
]
FormGrid
[
查看
]
Space
[
查看
]
Schema
中使用自定义非表单组件
antd v5.15.*
的 API 对组件进行修复
antd v5
自带的
css-in-js
添加组件样式
FormItem
的布局
Schema
动态添加
Component
组件修复:
Canscader
:将接口对应至
antd v5
最新的 API
Form
:为
FormLayout
提供上下文支持
FormButtonGroup
:适配
FormLayout
FormItem
:
FormLayout
,补充必选样式
Select.multiple
、
Switch
在布局尺寸变更时的适配
FormLayout
:
layout: inline
布局支持,修复必选等接口
原本是想为
@formily/antd-v5
提交 PR,由于当前
@formily/antd-v5
依赖的是
antd v5.6
导致 API 不兼容,又不能修改工程依赖,所以采用这种方式进行修改
---- 分割线 ----
/async
Select
[
查看
]
TreeSelect
[
查看
]
Cascader
[
查看
]
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
依赖输入内容每次加载
---- 分割线 ----
/controlled
几个受控模式:
props
受控、通过
ref
受控
observable
提供数据用于响应受控
Schema
整体受控:通过替换整个
form
和
schema
实现表单整体切换
Schema
片段联动:
form.clearFormGraph
回收字段模型,从而实现部分表单更新
observer
包裹函数组件响应字段更新,常用于自定义组件
form.setValues
包含的 4 个类型的值的区别(文档并没有说明)
React
组件中要响应表单数据,要么刷新整个表单;要么在受控组件上分别包裹
observer
响应对应的表单数据变化
SchemaField
),则建议通过“响应式值受控”,这样不会多余消费
React
组件性能
observable
:创建响应值,提供给控制者或
Form.values
,它包含一个
observable.ref
,在“实现异步数据源”有提到
observer
:响应组件的响应,用于在组件内容响应依赖数据的更新,暂且可以把它当作
memo
来理解
Observer
:用于在组件中提供一个响应区域,暂且可以把它当作
useMemo
来理解
在这个演示中,包含 4 个
@formily/react
内容:
RecursionField
、
useForm
、
useField
、
observer
,分别在演示备注中说明,稍后在具体章节再演示
---- 分割线 ----
/controlled
*()
,如:
*(.input1,input2)
field.query
或
form.values
或
schema
中使用
dependencies
hook
回调中,可以接受异步函数作为方法,作为异步联动,例如:
fetch
Effect
和
Schema
方式,以及被动逻辑和联动逻辑分别演示
主动和被动联动:
onFieldValueChange
主动联动,
onFieldReact
被动联动
schema
响应中使用
reactions
,区别在于:主动联动有
target
属性
schema
中,只有主动联动才需要指定生命周期
effect
FormEffectHooks
内容:
FieldEffectHooks
、
setFormState
、
setFieldState
、
SchemaReactions
、
FieldReaction
,稍后在具体章节再演示,也可以启动演示项目查看相关备注
---- 分割线 ----
/calculator
联动逻辑运算 + 自增表单的一个练习,提供
Markup Schema
和
Json Schema
---- 分割线 ----
/custom
connect
[
查看
]
mapProps
[
查看
]
mapReadPretty
[
查看
]
文档并没有提到具体内容,这里对前面所有内容归纳总结如下:
props
转发给组件
antd
组件
@formily/antd-v5
组件
observer
包裹组件
observer
组件中使用
hooks
connect
接入组件库
---- 分割线 ----
数据解构 (前后端数据差异兼容方案):
/destructor
将
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
---- 分割线 ----
/reactive
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
:局部batch
batch.abound
:异步 batch
batch.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
技术栈为主
更多往下查看单元测试
---- 分割线 ----
/core
我将使用
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
更多往下查看单元测试
---- 分割线 ----
/react
详细了解建议:查看本地演示或查看单元测试
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
不提供参数什么也不会发生
---- 分割线 ----
内部方法不再当前研究范围,不包含测试用例:
heart.spec.ts
[
查看
]
internals.spec.ts
[
查看
]
lifecycle.spec.ts
[
查看
]
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
setData
、
setContent
setData
、
setContent
setErrors
、
setWarnings
、
setSuccesss
、
setValidator
reactions
initialValue
0.input
:
[{ input: "123" }]
column
,可以直接忽略,直接查找虚拟节点下的
input
:
[{ input: "123" }]
input
:
[{ input: "123" }]
["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
[
查看
]
原理直接本地运行查看演示备注,除此之外还可以看看字段和模型的实践
React
.1:
Field
的
React
实现 [
查看
]
React
.2: 字段模型
SchemaField
实现 [
查看
]
React
.6: 字段特性 [
查看
]
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
---- 分割线 ----
在刷
Formily
的时候时常会有个疑问,
Formily
的优势是什么,对比诸如:
react-hook-form
、
zod
,亦或者直接使用
Antd
,我有什么非用
Formily
不可的地方吗?在刷完整个文档后我大致总结出这么几条:
Formily
天生为低代码生的
Schema
模型处理的能力
DMS
数据管理服务
几乎市面上所有的表单验证库也好,还是相关的库也好,它们都是围绕:验证、受控展开,但是很少会提到:模型渲染、数据分离,这应该就是
Formily
特有的地方了,它不仅仅是一个表单验证、受控的库。借助模型处理的能力,可以无限拓展、分离、组合不同的
schema
,实现、管理一个极其庞大的数据。
当然带来的问题就是学习成本的上升,要了解
Formily
,就要了解一套状态管理
Reactive
、了解副作用及声明周期和相关对象
Core
、了解一套现有的渲染引擎库
React
,以及对应的一套主题,如:
Antd
那什么时候选择使用
Formily
呢?
Formily
可能并非最优解
Formily
,例如:商城平台订单管理,分大类目、小类目、不同 SKU,库存,价格,优惠策略等等
Formily
,管理不同的客户群体,付费情况、不同链路渠道划分、统计等等