ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Formily Vue 中 useFormEffects Hook 详解:在自定义组件内向表单注入副作用逻辑

2026/9/23 16:53:58 拓冰建站 浏览量
Formily Vue 中 useFormEffects Hook 详解:在自定义组件内向表单注入副作用逻辑 前端UI组件【免费下载链接】formily Cross Device High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3项目地址https://gitcode.com/gh_mirrors/fo/formily点击查看免费下载导读useFormEffects是 Formily Vue 体系formily/vue中面向自定义组件开发者的一枚核心 Hook它允许你在组件内部直接向当前表单实例Form注入副作用effects逻辑从而优雅地实现字段联动、值同步、依赖响应等复杂场景化组件。本文将以官方 API 文档 use-form-effects.md 为主体骨架结合formily/vue与formily/core的源码实现与单元测试完整讲解它的签名、工作原理、生命周期管理、完整用例与常见注意事项读完即可在你的 Vue 2 / Vue 3 项目中放心使用。useFormEffects 是什么在 Formily 中副作用effects是一段在表单生命周期中运行的逻辑集合通常通过createForm({ effects })配置项注册在表单顶层用来监听字段变化、响应式驱动其他字段。官方文档对useFormEffects的描述是主要在自定义组件中往当前 Form 实例注入副作用逻辑用于实现一些较为复杂的场景化组件。它解决的问题是把副作用逻辑从表单全局配置下沉到组件内部。当一个自定义组件自身需要依赖其他字段、或者需要被复用并自带动效逻辑时使用useFormEffects可以做到组件自包含——组件挂载时注入 effects卸载时自动移除不会污染其他实例。useFormEffects由 packages/vue/src/hooks/useFormEffects.ts 实现并从 packages/vue/src/hooks/index.ts 统一对外导出与useForm、useField、useFieldSchema、useParentForm等 Hook 并列属于formily/vue组件开发工具箱的一部分。函数签名与参数官方文档给出的签名如下interface useFormEffects { (form: Form): void }参数form类型为Form即formily/core中的表单模型实例。实际上该参数由useFormEffects内部通过useForm()自动获取传给它的回调函数签名中携带form参数是为了在 effect 函数体内直接访问表单模型。返回值void无返回值。回调内部可使用的 API在回调函数中你可以调用formily/core导出的各类 effect hook例如onFieldReact、onFieldChange、onFieldValueChange、onFormInit、onFormMount、onFormSubmit等。一个最小调用形态import { useFormEffects } from formily/vue import { onFieldReact } from formily/core useFormEffects((form) { onFieldReact(target, (field) { // 字段联动逻辑 }) })与 createForm effects 的关系与差异在 Formily 常规用法中副作用是在创建表单时集中声明的import { createForm, onFieldReact } from formily/core const form createForm({ effects() { onFieldReact(custom.aa, (field) { field.value field.query(input).get(value) }) }, })而useFormEffects把同一类逻辑搬进了组件内部。两者的底层是一致的最终都会通过Form.addEffects注册到表单的 Heart 心跳模型LifeCycle 系统上。区别在于维度createForm effectsuseFormEffects声明位置表单创建时的全局配置自定义组件setup内部生命周期随表单实例随组件挂载/卸载隔离性全局生效仅组件内部注入可安全复用清理方式无需手动清理组件卸载时自动移除从 Form.ts 源码可以看到两者最终都汇聚到同一套机制addEffects (id: any, effects: IFormProps[effects]) { if (!this.heart.hasLifeCycles(id)) { this.heart.addLifeCycles(id, runEffects(this, effects)) } } setEffects (effects: IFormProps[effects]) { this.heart.setLifeCycles(runEffects(this, effects)) }createForm在内部通过setEffects或this.props.effects的lifecyclesgetter注册全局 effects而useFormEffects通过带唯一id的addEffects注入从而实现了可定向移除。底层原理一次完整的效果注册与清理useFormEffects的实现非常精简完整源码如下useFormEffects.tsimport { onBeforeUnmount, watchEffect } from vue-demi import { Form } from formily/core import { uid } from formily/shared import { useForm } from ./useForm export const useFormEffects (effects?: (form: Form) void): void { const formRef useForm() const stop watchEffect((onCleanup) { const id uid() formRef.value.addEffects(id, effects) onCleanup(() { formRef.value.removeEffects(id) }) }) onBeforeUnmount(() stop()) }整个机制可以拆解为四步获取当前表单通过useForm()useForm.ts从注入上下文中取出FormSymbol对应的表单实例得到RefForm。注册 effects在watchEffect首次执行时用uid()生成一个唯一 ID调用formRef.value.addEffects(id, effects)注册。由于addEffects内部有hasLifeCycles去重判断即使组件重复执行也不会重复注册同名效果。监听响应式依赖watchEffect会追踪formRef.value这一响应式引用。一旦FormProvider传入的form发生变化例如动态切换表单旧的 effects 会先通过onCleanup里的removeEffects(id)被清除然后重新注册到新表单上。组件卸载清理onBeforeUnmount(() stop())停止 watcher确保组件销毁时 effects 被彻底移除不产生内存泄漏也不会把联动逻辑残留在其他表单实例上。runEffects是真正的执行器定义于 effective.ts。它会设置全局状态GlobalState.effectStart在同步上下文中执行所有 effects 回调并把其中调用 effect hook 产生的LifeCycle收集起来返回给 Heart 模型export const runEffects Context( context?: Context, ...args: ((context: Context) void)[] ): LifeCycle[] { GlobalState.lifecycles [] GlobalState.context [] GlobalState.effectStart true GlobalState.effectEnd false if (isForm(context)) { FormEffectContext.provide(context) } args.forEach((effects) { if (isFn(effects)) { effects(context) } }) GlobalState.context [] GlobalState.effectStart false GlobalState.effectEnd true return GlobalState.lifecycles }值得注意的一点createEffectHook中规定 effect hook 只能在同步执行环境中调用GlobalState.effectStart为真否则会抛出 Effect hooks cannot be used in asynchronous function body 错误。因此useFormEffects回调内部的 effect hook 调用同样应保持同步。完整实战用例官方文档通过 dumi-previewer 嵌入了一个可运行 Demo源码位于 packages/vue/docs/demos/api/hooks/use-form-effects.vue。该 Demo 完整展示了两个方向的联动下面逐段解读。场景一自定义组件内部注入联动逻辑自定义组件Custom在setup阶段直接调用useFormEffects监听字段custom.bb的变化并让它跟随custom.aa的值const Custom defineComponent({ setup() { useFormEffects(() { onFieldReact(custom.bb, (field) { field.value field.query(.aa).get(value) }) }) return () h(div, {}, [ h(Field, { props: { name: aa, decorator: [Form.Item], component: [Input, { placeholder: aa }], }, }, {}), h(Field, { props: { name: bb, decorator: [Form.Item], component: [Input, { placeholder: bb }], }, }, {}), ]) }, })场景二表单顶层 effects 与组件内 effects 协同页面根组件通过createForm的effects注册另一条联动custom.aa跟随顶层字段input的值data() { const form createForm({ effects() { onFieldReact(custom.aa, (field) { field.value field.query(input).get(value) }) }, }) return { FormItem: Form.Item, Input, Custom, form } }模板结构如下template FormProvider :formform Field nameinput :decorator[FormItem] :component[Input, { placeholder: input }] / Field namecustom :decorator[FormItem] :component[Custom] / /FormProvider /template两条联动链路共同构成input→custom.aa→custom.bb。其中custom.aa的联动注册在表单顶层而custom.bb的联动注册在Custom组件内部——这正体现了useFormEffects的典型价值把属于组件的逻辑留在组件里。Demo 中同时展示了两种组件声明方式模板式Field与渲染函数式h(Field, ...)两者对useFormEffects无差别因为它依赖的是FormProvider注入的上下文而非具体渲染方式。测试验证行为已被单测锁定useFormEffects的行为在 packages/vue/src/tests/field.spec.ts 中有两个专门用例用例一值联动L334-372自定义组件内部调用useFormEffects注册onFieldChange(aa, [value], ...)当外部字段aa的值变化时把新值同步到自定义组件的字段值上。测试断言输入123后自定义组件渲染出123验证了组件内 effects 确实能响应表单字段变化。用例二表单切换时重新注册L374-417这是对useFormEffects动态能力的直接验证组件挂载后通过点击按钮把FormProvider的form替换成一个新的表单实例formRef.value createForm()并预设aa: text。测试断言新表单的值能被组件内 effects 捕获并渲染证明watchEffect追踪到了formRef.value的变化旧的 effects 通过onCleanup被移除新 effects 注册到了新表单上。这两个用例分别覆盖了注册生效与动态重注册两条关键路径是理解useFormEffects行为边界的最好参照。使用要点与注意事项综合文档、源码与测试使用useFormEffects时有几点值得注意必须位于FormProvider之下useFormEffects依赖useForm()注入的上下文脱离FormProvider使用将无法获取表单实例。在自定义组件的setup中调用即可无需关心渲染方式。回调保持同步effect hookonFieldReact、onFieldChange等不能在异步函数体中使用useFormEffects的回调内应直接同步调用这些 hook。清理是自动的组件卸载或表单切换时removeEffects(id)会自动执行无需手动清理这使组件具备良好的可复用性与隔离性。适用于复杂场景化组件官方定位是较为复杂的场景化组件例如需要感知外部字段值、自包含联动逻辑、可被多处复用的自定义字段组件。简单的静态组件无需引入 effects。与useField配合如测试用例所示useFormEffects常与useField一起使用在 effects 中通过fieldRef.value操作当前字段实现监听外部、驱动自身的模式。多组件互不干扰每个组件实例注册时使用独立的uid多个组件实例各自注入的 effects 不会互相覆盖。结语useFormEffects把 Formily 的副作用系统从表单级配置扩展到了组件级注入配合watchEffect的响应式追踪与onBeforeUnmount的自动清理让复杂联动逻辑能够以自包含、可复用、可卸载的方式封装进自定义组件。理解它的实现addEffects/removeEffects/runEffects的配合不仅能帮你写出更健壮的场景化组件也能让你在排查联动问题时更快定位到逻辑边界。如果想进一步了解与它配套的 Hook可继续阅读 use-form.md、use-field.md 与 use-parent-form.md。赞分享前端UI组件【免费下载链接】formily Cross Device High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3项目地址https://gitcode.com/gh_mirrors/fo/formily点击查看免费下载相关推荐Formily React 的 useFormEffects在自定义组件中注入表单副作用逻辑Formily React 的 useFormEffects在自定义组件中注入表单副作用逻辑 导读 useFormEffects 是 Formily Reac前端UI组件Formily 的 useFormEffects在自定义组件中注入表单副作用逻辑的完整指南Formily 的 useFormEffects在自定义组件中注入表单副作用逻辑的完整指南 useFormEffects 是 formily/react 提前端UI组件useForm 深度解析在 Formily Vue 自定义组件中读取表单实例useForm 深度解析在 Formily Vue 自定义组件中读取表单实例 导读 useForm 是 Formily 的 Vue 封装 formily/前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考