formily官网
Formily包含了三部分,core核心库、reactive响应库、react/vue 桥接层库

Core库
@formily/core 提供了核心能力,比如创建表单实例,监听表单各个生命周期等等,该仓库主要管理form实例、管理各字段状态,管理校验状态,以及上述三者之间的关系
Reactive库
@formily/reactive 提供了响应式状态管理,类似于mobx,更加方便的提供响应式数据修改组件。 状态修改后,主动刷新组件需要借助@formily/reactive-react 实现,类似于 UI = Observer(Fn(state))
React库
@formily/react 来接入内核数据,用来实现最终的表单交互效果
概述
目前使用React组件的方式接入formily框架,尚未接触到通过JSON Scheme的方式创建表单,主要通过组件方式创建表单。借助formily官方提供的UI桥接层库 @formily/react 提供的胶水组件Field,ArrayField,ObjectField实现表单快速搭建,记录总结在使用中遇到问题与解决方案,记录API与使用方法便于更快查询
创建表单
createForm
| 属性 |
描述 |
类型 |
默认值 |
| values |
表单值 |
Object |
{} |
| initialValues |
表单默认值 |
Object |
{} |
| pattern |
表单交互模式 |
“editable” | “disabled” | “readOnly” | “readPretty” |
“editable” |
| display |
表单显隐 |
“visible” | “hidden” | “none” |
“visible |
| hidden |
UI 隐藏 |
Boolean |
FALSE |
| visible |
显示/隐藏(数据隐藏) |
Boolean |
TRUE |
| editable |
是否可编辑 |
Boolean |
TRUE |
| disabled |
是否禁用 |
Boolean |
FALSE |
| readOnly |
是否只读 |
Boolean |
FALSE |
| readPretty |
是否是优雅阅读态 |
Boolean |
FALSE |
| effects |
副作用逻辑,用于实现各种联动逻辑 |
(form:Form)=>void |
|
| validateFirst |
是否只校验第一个非法规则 |
Boolean |
FALSE |
display => visible表示表单正常展示,hidden表示隐藏表单但隐藏字段,none表示隐藏表单与字段 同理hidden、visible意义相同
effects 中可以配置FormEffectHooks与FieldEffectHooks
用于配置表单的生命周期,主要是表单挂载,表单值的改变,表单提交,表单校验相关。
监听指定路径的表单字段的 挂载、校验、值变化等操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import React, { ReactChild } from "react"; import { createForm, Form, onFieldChange, onFormInit, onFormMount, onFormValuesChange } from "@formily/core"; import { FormProvider, Field, FormConsumer } from "@formily/react"; import { Input } from "antd";
const form = createForm({ effects: () => { onFormInit((form) => { console.log("表单初始化 : ", form.values); }); onFormMount((form) => { console.log("表单挂载 : ", form.values); }); onFormValuesChange((form) => { console.log("form values : ", form.values); }); onFieldChange("input1", (field) => { console.log("input1 field.values : ", field.value); const input = field.query("input"); const inputField = input.take(); inputField.setValue(field.value); }); } });
export default () => { return ( <FormProvider form={form}> <Field name="input" component={[Input, { placeholder: "Please Input" }]} /> <Field name="input1" component={[Input, { placeholder: "Please Input" }]} /> <Field name="input2" component={[Input, { placeholder: "Please Input" }]} /> <FormConsumer> {(form: Form) => { return JSON.stringify(form.values) as ReactChild; }} </FormConsumer> </FormProvider> ); };
|
自定义组件
借助@formily/react能力,自定义支持formily的组件。主要使用connect与mapProps实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React from "react"; import { Form, FormItemProps } from "antd"; import { connect, mapProps } from "@formily/react"; import { isDataField } from '@formily/core';
const Item = Form.Item;
export const FormItem: FormItemProps = connect<FormItemProps>( Item, mapProps((props, field) => { if(!isDataField(field)) return; console.log(field.selfErrors); return { ...props, help: field.selfErrors?.length ? field.selfErrors : undefined, }; }) );
|
用于创建一个字段模型,可以传入以下参数
| 属性 |
描述 |
类型 |
是否只读 |
默认值 |
| initialized |
字段是否已被初始化 |
Boolean |
否 |
FALSE |
| mounted |
字段是否已挂载 |
Boolean |
否 |
FALSE |
| unmounted |
字段是否已卸载 |
Boolean |
否 |
FALSE |
| address |
字段节点路径 |
FormPath |
是 |
|
| path |
字段数据路径 |
FormPath |
是 |
|
| title |
字段标题 |
FieldMessage |
否 |
“” |
| description |
字段描述 |
FieldMessage |
否 |
“” |
| loading |
字段加载状态 |
Boolean |
否 |
FALSE |
| validating |
字段是否正在校验 |
Boolean |
否 |
FALSE |
| modified |
字段子树是否被手动修改过 |
Boolean |
否 |
FALSE |
| selfModified |
字段自身是否被手动修改过 |
Boolean |
否 |
FALSE |
| active |
字段是否处于激活态 |
Boolean |
否 |
FALSE |
| visited |
字段是否被浏览过 |
Boolean |
否 |
FALSE |
| inputValue |
字段输入值 |
Any |
否 |
null |
| inputValues |
字段输入值集合 |
Array |
否 |
[] |
| dataSource |
字段数据源 |
Array |
否 |
[] |
| validator |
字段校验器 |
FieldValidator |
否 |
null |
| decorator |
字段装饰器 |
Any[] |
否 |
null |
| component |
字段组件 |
Any[] |
否 |
null |
| feedbacks |
字段反馈信息 |
IFieldFeedback [] |
否 |
[] |
| parent |
父级字段 |
GeneralField |
是 |
null |
| errors |
字段汇总(包含子节点)错误消息 |
IFormFeedback [] |
是 |
[] |
| warnings |
字段汇总(包含子节点)警告消息 |
IFormFeedback [] |
是 |
[] |
| successes |
字段汇总(包含子节点)成功消息 |
IFormFeedback [] |
是 |
[] |
| valid |
字段是否合法(包含子节点) |
Boolean |
否 |
TRUE |
| invalid |
字段是否非法(包含子节点) |
Boolean |
否 |
FALSE |
| value |
字段值 |
Any |
否 |
|
| initialValue |
字段默认值 |
Any |
否 |
|
| display |
字段展示状态 |
FieldDisplayTypes |
否 |
“visible” |
| pattern |
字段交互模式 |
FieldPatternTypes |
否 |
“editable” |
| required |
字段是否必填 |
Boolean |
否 |
FALSE |
| hidden |
字段是否隐藏 |
Boolean |
否 |
FALSE |
| visible |
字段是否显示 |
Boolean |
否 |
TRUE |
| disabled |
字段是否禁用 |
Boolean |
否 |
FALSE |
| readOnly |
字段是否只读 |
Boolean |
否 |
FALSE |
| readPretty |
字段是否为阅读态 |
Boolean |
否 |
FALSE |
| editable |
字段是可编辑 |
Boolean |
否 |
TRUE |
| validateStatus |
字段校验状态 |
FieldValidateStatus |
是 |
null |
| content |
字段内容,一般作为子节点 |
any |
否 |
null |
| data |
字段扩展属性 |
Object |
否 |
null |
| selfErrors |
字段自身错误消息 |
FieldMessage [] |
否 |
[] |
| selfWarnings |
字段自身警告消息 |
FieldMessage [] |
否 |
[] |
| selfSuccesses |
字段自身成功消息 |
FieldMessage [] |
否 |
[] |
| selfValid |
字段自身是否合法 |
Boolean |
否 |
TRUE |
| selfInvalid |
字段自身是否非法 |
Boolean |
否 |
FALSE |
| indexes |
字段数字索引集合 |
Number |
是 |
- |
| index |
字段数字索引,取 indexes 最后一个 |
Number |
是 |
- |
| reactions |
配置参数(字段响应器) |
FieldReaction[] | FieldReaction |
否 |
|
其中ArrayField提供了移动、删除、添加 等方法,ObjectField提供了添加、删除 操作
方法1:通过useField创建,使用该方法创建的组件如若需响应数据变化需要用observer包裹
formily工具方法 用于判断是否是指定对象类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import { FieldValidator, ArrayField as ArrayFieldType } from '@formily/core'; import { useField, observer } from '@formily/react'; import { Form } from 'antd';
const ArrayComponent = observer(() => { const field = useField<ArrayFieldType>() return ( <> <div> {field.value?.map((item, index) => ( <div key={index} style={{ display: 'flex-block', marginBottom: 10 }}> <Field name={index} component={[Input]} /> </div> ))} </div> <Button onClick={() => { field.push('') }} > Add </Button> </> ) }) const FormItem = observer(({ children }) => { const field = useField() return ( <Form.Item label={field.title} help={field.selfErrors?.length ? field.selfErrors : undefined} extra={field.description} validateStatus={field.validateStatus} > {children} </Form.Item> ) })
export default () => { return <> <Field name="name" title="姓名" required decorator={[FormItem]} component={[Input, { placeholder: 'Please Input' }]} /> <ArrayField name="habby" title="爱好" decorator={[FormItem]} component={[ArrayComponent]} > </> }
|
方法2:通过Field、ArrayField、ObjectField组件方式创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <Field name={`${basePath}.xxx`} title="标签" component={[ Input, { filled: true, showCount: true, placeholder: '请输入标签(2-7个字)', maxCount: 14 } ]} validator={{ triggerType: 'onInput', validator: (val: string) => { if (getLength(val) > 14 || getLength(val) < 4) { return '请输入标签(2-7个字)' } return '' } }} decorator={[ FormItem, { required: true, marginBottom: 24, } ]} />
|
类似于antd组件库中的Form组件,用于下发表单上下文给字段组件,负责整个表单状态
存在provider那么必然会有consumer,它的职责就是可以获取到实时的form表单数据
1 2 3 4 5 6 7 8 9 10 11 12 13
| import React from 'react' import { createForm } from '@formily/core' import { FormProvider, FormConsumer, Field } from '@formily/react' import { Input } from 'antd'
const form = createForm()
export default () => ( <FormProvider form={form}> <Field name="input" component={[Input]} /> <FormConsumer>{(form) => form.values.input}</FormConsumer> </FormProvider> )
|
该方法可以在组件使用,为form注入一些effect操作,同样可以将这些effect操作放到createForm的effects方法中, 这个hook没有办法监听onFormInit,执行到这个组件的时候那么Form表单必然是初始化好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React from 'react' import { createForm, onFieldReact } from '@formily/core' import { FormProvider, Field, useFormEffects } from '@formily/react' import { Input, Form } from 'antd'
const form = createForm({ effects() { onFieldReact('custom.aa', (field) => { field.value = field.query('input').get('value') }) }, })
const Custom = () => { useFormEffects(() => { onFieldReact('custom.bb', (field) => { field.value = field.query('.aa').get('value') }) }) return ( <div> <Field name="aa" decorator={[Form.Item]} component={[Input]} /> <Field name="bb" decorator={[Form.Item]} component={[Input]} /> </div> ) }
export default () => ( <FormProvider form={form}> <Field name="input" decorator={[Form.Item]} component={[Input]} /> <Field name="custom" decorator={[Form.Item]} component={[Custom]} /> </FormProvider> )
|
在普通场景里,我们可能只需要有Field就可以了,通过Field传入的reactions去做组件的关联效果。某些场景下希望,非表单组件可以控制表单组件展示内容或显隐逻辑,此时就需要一个状态来处理这个功能,但是useState无法影响表单数据样式展示,影响组件变化需要使用Observer 进行包裹
4.1 监听值变化
4.1.1 observable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| import { observable, autorun } from '@formily/reactive' function testObservable1() { const obs = observable({ aa: { bb: 123, }, cc: { dd: 123, }, }) autorun(() => { console.log('normal1', obs.aa.bb) })
obs.aa.bb = 44 autorun(() => { console.log('normal2', obs.cc.dd) })
obs.cc.dd = 123 }
function testObservable2() { const obs = observable({ aa: { bb: 123, }, cc: { dd: 123, }, ee: { ff: 123, }, gg: { hh: 123, }, })
autorun(() => { console.log('object1', obs.cc) })
obs.cc = { dd: 456 }
autorun(() => { console.log('object2', obs.ee) })
obs.ee = { ff: 123 }
autorun(() => { console.log('object3', obs.aa) })
console.log('testObservable2 set data') obs.aa.bb = 44 autorun(() => { console.log('object4', obs.gg.hh) })
console.log('testObservable2 set data2') obs.gg = { hh: 45 } }
function testObservable3() { const obs = observable({ aa: { bb: ['a'], }, }) autorun(() => { console.log('array1', obs.aa.bb) })
autorun(() => { console.log('array2', obs.aa.bb.length) })
autorun(() => { console.log('array3', obs.aa.bb[1]) })
console.log('testObservable3 set data') obs.aa.bb.push('cc') }
function testObservable4() { const obs = observable({ aa: { bb: ['a'], }, cc: '78', }) autorun(() => { console.log('other', obs.cc) })
console.log('testObservable4 set data') obs.aa.bb.push('cc') }
function testObservableShadow() { const obs = observable.shallow({ aa: { bb: 'a', }, cc: { dd: 'a', }, })
autorun(() => { console.log('shadow1', obs.aa.bb) })
console.log('testObservableShadow set data1') obs.aa.bb = '123'autorun(() => { console.log('shadow2', obs.cc) })
console.log('testObservableShadow set data2') obs.cc = { dd: 'c' } } export default function testObservable() { testObservable1() testObservable2() testObservable3() testObservable4() testObservableShadow() }
|
可以看到触发的规则为:
- number与string的基础类型,值比较发生变化了会触发
- object与array的复合类型,引用发生变化了会触发,object的字段添减不会触发,array的push和pop也不会触发
- array.length,它属于字段的基础类型变化,所以也会触发
- object与array类型,对于自己引用整个变化的时候,它也会触发子字段的触发
浅倾听shadow,只能处理表面一层的数据
4.1.2 复杂对象的obserable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
| import { observable, autorun } from '@formily/reactive' function testObservable1_object() { const obs = observable({ aa: { bb: 123, }, }) autorun(() => { console.log('normal1_object', obs.aa) })
console.log('1. sub assign') obs.aa.bb = 44 console.log('2. self assign') obs.aa = { bb: 789 } }
function testObservable2_object() { const obs = observable({ aa: { bb: 123, }, }) autorun(() => { const mk = obs.aa console.log('normal2_object') })
console.log('1. sub assign') obs.aa.bb = 44 console.log('2. self assign') obs.aa = { bb: 789 } }
function testObservable3_object() { const obs = observable({ aa: {}, }) autorun(() => { const mk = obs.aa console.log('normal3_object') })
console.log('1. self add property') obs.aa.bb = 4 console.log('2. self assign') obs.aa.bb = 5 console.log('3. self remove property') delete obs.aa.bb }
function testObservable4_object() { const obs = observable({ aa: {}, }) as any autorun(() => { for (const i in obs.aa) { console.log('nothing') } console.log('normal4_object') })
console.log('1. self add property') obs.aa.bb = 4 console.log('2. self assign') obs.aa.bb = 5 console.log('3. self remove property') delete obs.aa.bb }
function testObservable1_array() { const obs = observable({ aa: [] as number[], }) autorun(() => { const mk = obs.aa console.log('normal1_array') })
console.log('1.push') obs.aa.push(1)
console.log('2.assign') obs.aa[0] = 3 console.log('3.push') obs.aa.push(4)
console.log('4.pop') obs.aa.pop()
console.log('5.assign') obs.aa[0] = 5 }
function testObservable2_array() { const obs = observable({ aa: [] as number[], }) autorun(() => { console.log('normal2_array', obs.aa.length) })
console.log('1.push') obs.aa.push(1)
console.log('2.assign') obs.aa[0] = 3 console.log('3.push') obs.aa.push(4)
console.log('4.pop') obs.aa.pop()
console.log('5.assign') obs.aa[0] = 5 }
function testObservable3_array() { const obs = observable({ aa: [] as any[], }) autorun(() => { obs.aa.map((item) => '') console.log('normal3_array') })
console.log('1.push') obs.aa.push(1)
console.log('2.assign') obs.aa[0] = 3 console.log('3.push') obs.aa.push({})
console.log('4.inner assign') obs.aa[1].kk = 3 console.log('5.pop') obs.aa.pop()
console.log('6.assign') obs.aa[0] = 5 }
export default function testObservableCaseTwo() { testObservable1_object() testObservable2_object() testObservable3_object() testObservable4_object() testObservable1_array() testObservable2_array() testObservable3_array() }
|
对于数组与对象类型的触发,他们的规则是:
- 如果只是对数组或对象整个进行get操作(console,或者赋值到其他变量),那么只有整个对象都被set的时候才会被触发。
- 对数组或对象进行遍历或长度操作,例如for,map或者length行为,那么执行对象的addProperty或者push,pop都会有通知
- 数组的map操作特别一点,但其实它的回调闭包里面包含了元素的get操作,所以对元素的set操作会得到触发。这条规则其实就是第一条规则而已。
- 注意,对于子字段的变化,父字段不会收到通知。反过来,父字段整个变化的时候,子字段总是可以收到通知
至此,我们大概能推测到Observable的实现是:
- 包装对象对属性的get与set操作,当get操作触发的时候,将当前闭包函数到subscribe保存起来。当同一个对象的set操作发生时,拉取对应属性的闭包函数,然后publish对应的闭包函数,并触发子对象的通知。
- 包装对象对方法的操作,for,map,length,当这些方法触发的时候,将当前闭包函数到subscribe保存起来。当同一个对象的addProperty,removeProperty,push,pop触发的时候,publish对应的闭包函数。
4.1.3 observable.ref
1 2 3 4 5 6 7 8 9 10 11
| import { autorun, observable } from '@formily/reactive' export default function testRef() { const ref = observable.ref(1)
autorun(() => { console.log(ref.value) })
ref.value = 123 }
|
在js环境中,只有object类型才能侦听数据set操作。对于一个基础类型的数据,无法倾听它的set操作。ref操作就是为了包装它实现的
4.1.4 observable.box
1 2 3 4 5 6 7 8 9 10 11
| import { autorun, observable } from '@formily/reactive' export default function testRef() { const box = observable.box(1)
autorun(() => { console.log(box.get()) })
box.set(123) }
|
box类型也是类似ref类型的一样的功能,它只是换成了用get与set方法来包装基础类型而已
4.2 收集get操作并自动触发
4.2.1 autorun
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import { autorun, observable } from "@formily/reactive"
function testAutoRun1(){ const obs = observable({ aa:78 })
const stop = autorun(() => { console.log(obs.aa) })
obs.aa = 123 stop();
obs.aa = 789 }
function testAutoRun2(){ const obs = observable({ aa:1, bb:3 })
autorun(()=>{ if( obs.aa == 1 || obs.bb == 2){ console.log('true'); }else{ console.log("false"); } })
obs.bb = 4 obs.aa = 2 obs.bb = 2 }
export default function testAutoRun(){ testAutoRun1() testAutoRun2() }
|
autorun就是在闭包中批量收集对数据get操作的依赖,当数据变化的时候,就会自动重新执行一次闭包,并且重新收集get操作的依赖
4.2.2 computed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { autorun, observable } from '@formily/reactive'
export default function testComputed() { const obs = observable({ aa: 11, bb: 22, })
const computed = observable.computed(() => obs.aa + obs.bb)
autorun(() => { console.log(computed.value) })
obs.aa = 33 }
|
computed的想法也是很直观,autorun是数据变化时重新执行闭包,computed是数据变化重新计算派生值
4.2.3 reaction
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { observable, reaction, autorun } from '@formily/reactive'
function testReaction1() { const obs = observable({ aa: 1, bb: 2, })
const dispose = reaction(() => obs.aa + obs.bb, console.log)
obs.aa = 4 dispose() }
function testReaction2() { const obs = observable({ aa: 1, bb: 2, })
const computeValue = observable.computed(() => obs.aa + obs.bb)
const dispose = autorun(() => { console.log(computeValue.value) })
obs.aa = 4 dispose() } export default function testReaction() { testReaction1() testReaction2() }
|
reaction其实就是computed与autorun的组合而已,数据变化的时候,先重新计算派生值,然后拿派生值作为参数运行闭包
4.2.4 tracker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { observable, Tracker } from '@formily/reactive'
export default function testTracker() { const obs = observable({ aa: 11, })
const view = () => { console.log('view go!!!') console.log(obs.aa) }
const tracker = new Tracker(() => { console.log('tracker other')
tracker.track(view) })
console.log('tracker first') tracker.track(view)
obs.aa = 22
tracker.dispose() }
|
tracker是更为底层的方法,一般都很少用。autorun与computed都是数据变化的时候,自动重新触发和重新收集get操作依赖。而tracker就是仅一次触发,要想下次触发就必须手动调用track方法
4.2.5 observe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { observable, observe } from '@formily/reactive'
export default function testObserve() { const obs = observable({ aa: 11, bb: [1], })
const dispose = observe(obs, (change) => { console.log('observe1', change) })
obs.aa = 22 const dispose2 = observe(obs.bb, (change) => { console.log('observe2', change) })
obs.bb.push(1) obs.bb.push(2)
dispose() dispose2() }
|
observe的方法更为底层,它会输出数据是如何变化的这个信息,并不会重新收集依赖
4.3 批量触发
4.3.1 batch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { observable, autorun, batch } from '@formily/reactive' export default function testBatch() { const obs = observable<any>({ aa: 1, })
autorun(() => { console.log(obs.aa, obs.bb) })
batch(() => { obs.aa = 321 obs.bb = 'dddd' }) }
|
batch操作时,只有batch方法执行完成以后,才批量触发一次autorun的通知,这样能提高性能,避免重复触发。
4.3.2 batch.scope
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { batch, observable, autorun } from '@formily/reactive' export default function testBatchScope() { const obs = observable<any>({})
autorun(() => { console.log(obs.aa, obs.bb, obs.cc, obs.dd) })
batch(() => { batch.scope(() => { obs.aa = 123 })
batch.scope(() => { obs.cc = 'ccccc' })
obs.bb = 321 obs.dd = 'dddd' }) }
|
batch.scope就是支持嵌套批量的能力而已,就像事务里面嵌套事务
4.4 倾听值变化的语法糖
reative提供了语法糖的方法,来帮助我们更快地建立字段的observable,ref,box,shallow,方法的batch这些操作而已
4.4.1 action
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { observable, action, autorun } from '@formily/reactive'
export default function testAction() { const obs = observable({ aa: 1, bb: 2, })
autorun(() => { console.log(obs.aa, obs.bb) })
const method = action(() => { obs.aa = 123 obs.bb = 321 })
method() }
|
action能快速帮助将一个闭包,用batch包围起来
4.4.2 define
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import { define, observable, autorun, action } from '@formily/reactive' export default function testDefine() { class DomainModel { deep = { aa: 1 }
shallow = {}
box = 0 ref = '' constructor() { define(this, { deep: observable, shallow: observable.shallow, box: observable.box, ref: observable.ref, computed: observable.computed, go: action, }) }
get computed() { return this.deep.aa + this.box.get() }
go(aa, box) { this.deep.aa = aa this.box.set(box) } }
const model = new DomainModel()
autorun(() => { console.log(model.computed) })
model.go(1, 2) model.go(1, 2) model.go(3, 4) }
|
define的这个方法挺不好的,建议不要用,对TypeScript的支持不好,而且也不快捷
4.4.3 model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { model, autorun } from '@formily/reactive'
export default function testModel() { const obs = model({ aa: 1, bb: 2,
get cc() { return this.aa + this.bb },
update(aa: number, bb: number) { this.aa = aa this.bb = bb }, })
autorun(() => { console.log(obs.cc) })
obs.aa = 3 obs.update(4, 6) }
|
model这个语法糖超级好:
- 字段自动用observable包装
- getter与setter方法用computed包装
- 普通方法用action包装
4.5 结合react使用
利用@formily/reactive-react 或 @formily/react 都可,使用导出的Observer组件包裹函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { Field, Observer } from '@formily/react'; import { observable } from '@formily/reactive'; const state = observable({ name: 1 }) export default () => { return <Observer> { () => <> { state.name } <Button onClick={() => (state.name += 1)}>+1</Button> </> } <Observer>
}
|
路径
FormPath 是 Formily 2.0 中一个能力,相比于 Formily 1.x 中的 cool-path ,它提供了更加清晰、易用、强大的路径管理能力。
解构表达式
- 解构表达式会作为点路径的某个节点,我们可以把它看做一个普通字符串节点,只是在数据操作时会生效,所以在匹配语法中只需要把解构表达式作为普通节点节点来匹配即可
- 在 setIn 中使用解构路径,数据会被解构
- 在 getIn 中使用解构路径,数据会被重组
1 2 3 4 5 6 7 8
| import { FormPath } from '@formily/core'
const target = {}
FormPath.setIn(target, 'parent.[aa,bb]', [11, 22]) console.log(target) console.log(FormPath.getIn(target, 'parent.[aa,bb]')) console.log(FormPath.parse('parent.[aa,bb]').toString())
|
相对路径
- n 个点代表往前 n-1 步,中括号中可以用下标计算表达式:[+]代表当前下标+1,[-]代表当前下标-1,[+n]代表当前下标+n,[-n]代表当前下标-n
1
| console.log(FormPath.parse('..[+10].dd', 'aa.1.cc').toString())
|
全匹配/局部匹配/分组匹配/反向匹配
1 2 3 4 5 6 7 8 9 10 11
| import { FormPath } from '@formily/core'
console.log(FormPath.parse('*').match('aa.bb'))
console.log(FormPath.parse('aa.*.cc').match('aa.bb.cc'))
console.log( FormPath.parse('aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc').match('aa.bb.cc') )
console.log(FormPath.parse('*(!aa,bb,cc)').match('kk'))
|
注意:
field的query可以基于当前字段查询相邻字段,也就是说field的query可以基于同一父path去查询,而Form的query则无限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
... <Field name="info.age" component={[ Input ]} reactions={[ (field) => { console.log(field.query('.name').value()) console.log(field.query('..hobby').value()) } ]} > </Field> ...
|
校验类型参考
简单示例:
1 2 3 4 5 6 7 8 9 10
| <Field required validator={{ triggerType: 'onBlur', required: true, validator: (value) => { return "" } }} ></Field>
|
引用
- Formily的Reactive的经验汇总