Formily 响应式 React 绑定指南:observer 与 Observer 的依赖追踪原理与实战
2026/9/24 3:27:57 网站建设 项目流程
  • 前端
  • 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/reactive-react是 Formily 响应式核心@formily/reactive与 React 之间的桥梁。本文围绕其官方 API 文档 observer.zh-CN.md 展开,深入讲解observer(高阶组件)与Observer(RenderProps 组件)的使用方式、配置项,并结合仓库源码剖析其背后的依赖收集、批量调度与垃圾回收机制。读完本文,你将能够在 React 组件中精确地把响应式数据(如 Formily 表单状态)绑定到视图,实现"数据变了视图自动更新"的局部细粒度渲染。


一、observer:将 Function Component 变成响应式 Reaction

1.1 描述与适用前提

observer@formily/reactive-react导出的高阶组件(HOC),其作用可以概括为:

在 React 中,将 Function Component 变成 Reaction。每次视图重新渲染时都会收集依赖,依赖更新后视图自动重新渲染。

结合 Formily 的场景来理解:当你把observable包裹的表单状态(如Form实例、字段值)直接读取到组件渲染函数中时,observer会在渲染过程中记录这些被读取的响应式属性,并在它们发生变化时触发组件重渲染,从而省去手写useEffect+setState的样板代码。

注意(文档中的<Alert>强调)observer只支持 Function Component。Class Component 无法被包装,因为其渲染逻辑无法被包裹在Tracker的追踪函数内部。

1.2 签名与配置项

interface IObserverOptions { forwardRef?: boolean //是否透传引用 scheduler?: (updater: () => void) => void //调度器,可以手动控制更新时机 displayName?: string //包装后的组件的displayName } interface observer<T extends React.FC> { (component: T, options?: IObserverOptions): T }

三个配置项的作用与源码对应关系如下:

配置项类型默认值作用源码依据
forwardRefbooleanfalse是否将ref透传给被包装组件;为true时返回类型会扩展出ref属性observer.ts 中通过React.forwardRef包装
scheduler(updater: () => void) => void内置强制更新自定义更新调度器,可控制依赖变化后重渲染的时机(如合并多次更新、延迟渲染)useObserver.ts
displayNamestring设置包装后组件的displayName,便于 React DevTools 调试observer.ts

从源码看,observer的内部实现流程为(observer.ts):

  1. 合并默认选项,forwardRef默认为false
  2. 根据forwardRef选择用forwardRef还是普通函数包装组件,并在包装函数内部调用useObserver(() => component(props))
  3. React.memo包裹包装组件,获得浅比较 props 的缓存能力;
  4. 通过hoistNonReactStatics把原组件的静态属性拷贝到包装组件上(该依赖见 package.json);
  5. 若传了displayName,则覆盖memoComponent.displayName

1.3 官方用例:双向绑定输入框

/** * defaultShowCode: true */ import React from 'react' import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-react' const obs = observable({ value: 'Hello world', }) export default observer(() => { return ( <div> <div> <input style={{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }} value={obs.value} onChange={(e) => { obs.value = e.target.value }} /> </div> <div>{obs.value}</div> </div> ) })

在这个例子中,obs.valueobservable变成响应式数据。组件渲染时读取了obs.valueobserver便把它登记为依赖;当输入框触发onChange修改obs.value后,依赖变更会驱动整个组件重新渲染,输入框和展示文本同步更新。这正是 Formily 中表单控件能"不写一行setState"就跟随字段值变化的核心机制。


二、Observer:类似 Vue 响应式 Slot 的局部精确渲染

2.1 描述

Observer是一个直接导出的组件,其定位与observer不同:

类似于 Vue 的响应式 Slot,它接收一个 Function RenderProps。只要在 Function 内部消费到的任何响应式数据,都会随数据变化而自动重新渲染,也更容易实现局部精确渲染。

换言之,Observer不需要包裹整个组件,而是只把"渲染函数"这一小段逻辑变成反应式作用域。你可以在一个组件内部任意位置插入多个Observer,各自独立追踪依赖、独立更新,从而实现最小粒度的精确渲染。

2.2 签名

interface IObserverProps { children?: () => React.ReactElement } type Observer = React.FC<React.PropsWithChildren<IObserverProps>>

在仓库的实际类型定义中(types.ts),children被放宽为(() => React.ReactElement) | React.ReactNode,即既支持函数形式,也支持静态节点;observer.ts 中Observer的实现会判断children是否为函数,是则执行,然后把结果渲染进Fragment

2.3 官方用例:输入框与展示文本分别独立响应

/** * defaultShowCode: true */ import React from 'react' import { observable } from '@formily/reactive' import { Observer } from '@formily/reactive-react' const obs = observable({ value: 'Hello world', }) export default () => { return ( <div> <div> <Observer> {() => ( <input style={{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }} value={obs.value} onChange={(e) => { obs.value = e.target.value }} /> )} </Observer> </div> <Observer>{() => <div>{obs.value}</div>}</Observer> </div> ) }

与上一节observer的写法对比可见:外层组件本身没有被包装,只有两个Observer内部的渲染函数各自建立了一条"响应式作用域"。输入框区域和展示区域各自追踪对obs.value的读取,任何一个数据变更都只触发对应Observer片段的更新——这正是"局部精确渲染"的实际效果。在 Formily 的组件实现中,这种模式被广泛用于"只重渲染被字段值影响的片段"。


三、源码级原理:从useObserverTracker的依赖追踪链路

observerObserver只是外层形态不同,底层都汇聚到同一个 Hook:useObserver

3.1useObserver:追踪器与调度器

useObserver.ts 的核心逻辑如下:

export const useObserver = <T extends () => any>( view: T, options?: IObserverOptions ): ReturnType<T> => { const forceUpdate = useForceUpdate() const tracker = useCompatFactory( () => new Tracker(() => { if (typeof options?.scheduler === 'function') { options.scheduler(forceUpdate) } else { forceUpdate() } }, options?.displayName) ) return tracker.track(view) }

关键点:

  • 通过useCompatFactory惰性创建一个Tracker实例(整个组件生命周期内只创建一次);
  • Tracker的调度回调中,优先使用用户传入的scheduler,否则调用内置的forceUpdate
  • 每次渲染时调用tracker.track(view),把view函数放进追踪上下文执行,从而收集依赖。

3.2Tracker:Reaction 的收集与释放

Tracker定义在响应式核心包中(tracker.ts)。其track方法会在执行追踪函数前:

  1. 调用releaseBindingReactions(this.track)释放上一次绑定的依赖关系;
  2. 进入batchStart(),把当前 reaction 压入ReactionStack
  3. 执行tracker()(即渲染函数),期间读取的响应式属性会被登记为依赖;
  4. finally中弹出栈、batchEnd()结束批量事务。

当被追踪的依赖发生变更时,tracker_scheduler会被触发,进而驱动forceUpdate完成重渲染;同时dispose()会通过disposeBindingReactions解除全部依赖绑定(组件卸载时使用)。

3.3useForceUpdate:渲染队列与 StrictMode 兼容

useForceUpdate.ts 内置了一套轻量的渲染调度策略:

  • 维护模块级的RENDER_COUNT计数器与RENDER_QUEUESet);
  • 若在首次渲染阶段(firstRenderedReffalse)触发更新,只标记needUpdateRef,待 layout effect 后再真正setState——这是为了兼容StrictMode下"第一次渲染函数被二次执行"导致依赖无法被快速回收的问题(源码注释中对此有明确说明);
  • 若已有渲染在进行(RENDER_COUNT.value > 0),更新请求会被加入队列,待当前渲染批次结束后统一消费,起到合并多次更新、避免渲染风暴的作用;
  • useLayoutEffect阶段会执行一次性回调并触发useDidUpdate中的收尾逻辑,递减计数并清空队列。

3.4useCompatFactory与垃圾回收:StrictMode/ConcurrentMode 下的安全卸载

useCompatFactory.ts 负责 Tracker 实例的创建与销毁:

  • useRef保存实例,首次渲染时创建;useState中保留一个"被 React 持有的对象"作为 GC 注册目标;
  • 由于 StrictMode/ConcurrentMode 下组件可能无法正确触发 UnMount(源码注释原文),它额外引入 gc.ts 中的GarbageCollector:优先利用FinalizationRegistry在对象被回收时清理 Tracker(dispose),在不支持FinalizationRegistry的环境退化为 10 秒定时兜底清理;
  • 同时保留useCompatEffect的清理函数,在正常卸载路径下同步调用dispose(),实现双保险。

可以推断,这一整套设计的目标是:在并发渲染、严格模式等复杂生命周期下,既保证依赖追踪器不泄漏,也保证视图更新不会在卸载后触发。


四、observerObserver的选型建议

对比维度observerObserver
形态高阶组件(HOC),包住整个 Function Component组件 + RenderProps,包住片段
依赖范围整个组件渲染函数的全部响应式读取仅 children 渲染函数内的读取
更新粒度组件级重渲染(配合memo做 props 浅比较)片段级精确渲染,多个Observer互不干扰
适用场景一个组件整体绑定响应式状态(如 Formily 中的表单控件组件)组件内部只有局部区域依赖响应式数据,希望减少无效渲染

实践建议

  • 当整个页面/区块都依赖表单数据时,用observer包裹最外层组件最省事;
  • 当外层组件是"稳定的壳"、只有局部区域会随数据变化时,用多个Observer切片渲染,性能更优;
  • 需要透传ref时,给observer{ forwardRef: true }
  • 需要把多次连续更新合并到同一帧、或接入外部调度(如requestAnimationFrame)时,通过scheduler自定义更新时机。

五、快速上手:最小可运行示例

仓库内 README.md 给出了一个更简洁的入门示例:

import React from 'react' import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-react' const obs = observable({ count: 0, }) export default observer(() => { return ( <div> {obs.count} <button onClick={() => { obs.count++ }} > Click </button> </div> ) })

将其放入任何 React 应用(配合@formily/reactive@formily/reactive-react两个包)即可运行:点击按钮修改obs.count,视图自动刷新,无需任何手动setState。如需深入了解底层的响应式原语(observableTracker、批量事务等),可继续阅读 packages/reactive 包内源码及其 API 文档。


总结

observerObserver是 Formily 响应式体系接入 React 的两个核心出口:observer将整个函数组件变为依赖自动追踪的 Reaction,Observer则提供"响应式 Slot"式的局部精确渲染。通过阅读 observer.ts、useObserver.ts、useForceUpdate.ts 与 tracker.ts 等源码,可以清晰看到一条完整的链路:渲染函数 →Tracker.track收集依赖 → 依赖变更触发调度 →forceUpdate合并批量渲染 → 卸载/GC 释放绑定。掌握这两个 API,就掌握了在 Formily 中把响应式状态与 React 视图高效绑定的核心能力。

  • 前端
  • 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
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询