- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
导读
@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 }三个配置项的作用与源码对应关系如下:
| 配置项 | 类型 | 默认值 | 作用 | 源码依据 |
|---|---|---|---|---|
forwardRef | boolean | false | 是否将ref透传给被包装组件;为true时返回类型会扩展出ref属性 | observer.ts 中通过React.forwardRef包装 |
scheduler | (updater: () => void) => void | 内置强制更新 | 自定义更新调度器,可控制依赖变化后重渲染的时机(如合并多次更新、延迟渲染) | useObserver.ts |
displayName | string | 无 | 设置包装后组件的displayName,便于 React DevTools 调试 | observer.ts |
从源码看,observer的内部实现流程为(observer.ts):
- 合并默认选项,
forwardRef默认为false; - 根据
forwardRef选择用forwardRef还是普通函数包装组件,并在包装函数内部调用useObserver(() => component(props)); - 用
React.memo包裹包装组件,获得浅比较 props 的缓存能力; - 通过
hoistNonReactStatics把原组件的静态属性拷贝到包装组件上(该依赖见 package.json); - 若传了
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.value被observable变成响应式数据。组件渲染时读取了obs.value,observer便把它登记为依赖;当输入框触发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 的组件实现中,这种模式被广泛用于"只重渲染被字段值影响的片段"。
三、源码级原理:从useObserver到Tracker的依赖追踪链路
observer与Observer只是外层形态不同,底层都汇聚到同一个 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方法会在执行追踪函数前:
- 调用
releaseBindingReactions(this.track)释放上一次绑定的依赖关系; - 进入
batchStart(),把当前 reaction 压入ReactionStack; - 执行
tracker()(即渲染函数),期间读取的响应式属性会被登记为依赖; finally中弹出栈、batchEnd()结束批量事务。
当被追踪的依赖发生变更时,tracker的_scheduler会被触发,进而驱动forceUpdate完成重渲染;同时dispose()会通过disposeBindingReactions解除全部依赖绑定(组件卸载时使用)。
3.3useForceUpdate:渲染队列与 StrictMode 兼容
useForceUpdate.ts 内置了一套轻量的渲染调度策略:
- 维护模块级的
RENDER_COUNT计数器与RENDER_QUEUE(Set); - 若在首次渲染阶段(
firstRenderedRef为false)触发更新,只标记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(),实现双保险。
可以推断,这一整套设计的目标是:在并发渲染、严格模式等复杂生命周期下,既保证依赖追踪器不泄漏,也保证视图更新不会在卸载后触发。
四、observer与Observer的选型建议
| 对比维度 | observer | Observer |
|---|---|---|
| 形态 | 高阶组件(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。如需深入了解底层的响应式原语(observable、Tracker、批量事务等),可继续阅读 packages/reactive 包内源码及其 API 文档。
总结
observer与Observer是 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
相关推荐
Formily 响应式渲染:@formily/reactive-react 的 observer 与 Observer 使用指南
Formily 响应式渲染:@formily/reactive react 的 observer 与 Observer 使用指南 本文是 Formily 官方文
前端UI组件Formily Reactive React 的 observer 与 Observer:让函数组件与响应式数据深度绑定
Formily Reactive React 的 observer 与 Observer:让函数组件与响应式数据深度绑定 导读 在 Formily 的表单体系中
前端UI组件Formily 响应式渲染指南:深入理解 observer HOC 与 Observer 组件
Formily 响应式渲染指南:深入理解 observer HOC 与 Observer 组件 导读 本文以 Formily 官方文档 observer.md
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考