作者:陈南晓
背景
古茗在中后台的场景中大量的使用 formily 来解决问题。在小陈首次使用 formily 时第一感受,这玩意儿咋那么难用,能不用吗?(现在改成 antd 来写还来得及不)跟老李反馈了初识 formily 遇到的难处,老李听后细心解答了古茗的中后台在使用表单始终会存在几个问题(表单数据管理、表单字段依赖、表单精准更新等等)。
formily 仅仅作为一个数据载体帮我们去处理这类问题,不使用 formily 也照样存在你反馈的难处(但上手成本会低点🐶)。小陈回头一想是那么一回事,OK,I’m fine 是真的被他打败。那我们来看一下 formily 是如何解决中后台表单数据管理、字段依赖、精准更新问题吧~
表单数据管理
当使用 createForm 创建出一个表单模型,主要包括 FormGraph 和 FormHeart 两个部分。
其中:
- FormGraph:表单是一个 Form 对象,表单字段是一个 Field 对象(每一个表单字段都对应一个独立的 Field ),它们的状态都由各自内部维护。这些都可以看成一个个的节点,最终都统一由 FormGraph 进行管理。
- FormHeart:管理的是表单的生命周期,里面包括了表单的挂载,字段状态改变等等生命周期事件。
我们知道在 Input 组件中输入一个值,Input 组件对应的 Field 字段的 value 会更新以及 Form 中的 value 也会更新,反过来 Form 或 Field 中的 value 改变也会更新 Input 组的值,我们来看下它们是如何进行通信的
其中:
- formState:维护着表单所有字段的 value
- fieldState:维护着当前字段的 value
- component:是每个字段对应的展示层组件,可以是 Input 或者 Select,也可以是其它的自定义组件
Form和Field数据通信
Form 和 Field 是通过发布订阅的模式进行通信,当 field.value 改变会通知 form 表单更新 value , form 中的 value 改变也会通知 field 更新 value 。
formily 内部实现
1 | jsx复制代码// form.value 变化通知 field |
Field与Component数据通信
一个 Field 对应一个 Component ,当用户在 Component 中输入时,会触发对应的 onChange 事件,该事件内部把用户输入的值传递给 Field 里。在 Field 值变动时,会将 field.value 通过 props.value 的形式传递给 Component,最终达到双向绑定的效果。
formily 内部实现
1 | jsx复制代码const renderComponent = () => { |
这里的 onChange 事件 field.onInput 会把用户输入的 value 赋值给 field.value 。反过来 field.value 改变会通过响应式知道哪个组件依赖它,并对其重新渲染。
表单字段联动
字段联动是指表单中一个字段依赖于其他字段的值,当其值发生变化时,相关联的表单元素会做出相应的变化或更新。那在 formily 中是如何实现表单联动的呢?
在 formily 中表单的联动效果,本质上也是一个“响应式”模型,实现原理借助了 formily/core ➕ formily/reactive 。
- 依赖收集
在 formily/core 中实例化 Field 实例中会执行 createReactions ,实现对依赖的收集
1 | jsx复制代码const createReactions = (field: GeneralField) => { |
我们再进一步看看 autorun 是个啥玩意,怎么就能够收集依赖了呢?这里实现简易版的 autorun(关于“响应式”模型感兴趣的小伙伴可以看看这篇从零开始撸一个「响应式」框架
1 | jsx复制代码let ReactionStack; |
由于 reaction(field) 中有引用 fieldState ,所以触发 baseHandler 中的 get 属性,实现对依赖的收集。
- 依赖监听
在上述 autorun 中实现的 get 属性实现了对依赖的收集,其中的 set 属性就是对依赖的监听,当依赖的值发生改变时会触发 set,执行 reaction 更新表单字段。
表单的联动在表单中是非常重要的,包含了字段间的各种关系。同时字段与字段关联时,还要保证不影响表单性能,下面我们再走进 formily 深处,看看它凭啥是表单高性能的解决方案。
表单精准更新
表单的精准更新本质上也是响应式原理,类似于表单组件里面引用了子组件,该如何正确的知道是哪个组件依赖了当前表单字段,另外当表单字段更新时如何做到只更新对应的子组件。
formily 中把上述的 autorun 中的全局变量 ReactionStack 改成一个栈形式,记录调用依赖的函数。下面我们来看一个栗子,假如 A 组件中引用了 B 组件,B 组件中引用了 C 组件。最终的形式 ,然而只有 C 组件使用的该依赖值,我们只需要重新渲染 C 组件即可,A、B 组件不需要重新渲染。
在依次执行 A、B、C 组件时,当执行到 C 组件时,发现有对依赖进行引用,对其进行依赖收集,即 ReactionStack[ReactionStack.length - 1] 此时为 C 组件,初始化结束后依次按顺序出栈。之后依赖的值更改时由于收集到的依赖是 C 组件,所以当依赖值改变时,只会重新渲染 C 组件。
1 | jsx复制代码// 由于代码过多,只列出如何实现精准刷新的与上文的区别 |
总结
在中后台的业务开发中与表单的邂逅是不可避免的,社区上也有各式各样的表单解决方案,正式练习快满一年的小陈同学从一开始对 formily 的疯狂 diss ,到现在发现更多只是使用方式的差别。🙏大家的阅读,如文章中有错误👏评论,还有如果有建议或者不同的想法,欢迎留言 也可以期待一下我们的下一篇文章 salute🫡~~
附录
最后
🌟 招聘信息:
📚 小茗文章推荐:
关注公众号「Goodme前端团队」,获取更多干货实践,欢迎交流分享~
本文转载自: 掘金