尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Formily Reactive React 的 observer 与 Observer:让函数组件与响应式数据深度绑定

Formily Reactive React 的 observer 与 Observer:让函数组件与响应式数据深度绑定 前端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 的表单体系中formily/reactive提供了基于依赖追踪的响应式数据模型而formily/reactive-react包中的observer与Observer则是打通「响应式数据」与「React 视图」的关键桥梁。本文将以 observer.md 文档为骨架结合 packages/reactive-react 的源码实现系统讲解如何将普通函数组件转化为自动收集依赖、自动重渲染的响应式组件以及如何利用Observer在组件内部实现局部精确渲染。读完本文你将能够为任意函数组件接入响应式依赖追踪、通过scheduler自定义刷新时机、借助Observer缩小渲染粒度并理解其背后Tracker依赖收集与垃圾回收的真实工作原理。observer把函数组件变成 Reaction概念与使用前提observer是formily/reactive-react导出的核心高阶函数其作用正如文档描述在 React 中将函数组件转换为一个 Reaction依赖追踪的响应式任务视图每次重新渲染时都会重新收集依赖当被依赖的响应式数据发生变化时组件会自动重新渲染。需要注意的关键约束文档中以 Alert 明确提示仅支持函数组件Function Component。这是因为依赖的收集与销毁依赖useObserver等 Hooks 机制而 Hooks 只能在函数组件中使用。类组件无法直接使用observer包装需要借助其他方式如Observer组件或手动 Hooks接入响应式。在 Formily 生态中formily/react的Field、FormProvider等组件正是建立在observer之上让表单字段的显示、校验、联动在数据变化时自动刷新。函数签名与选项文档给出的签名如下interface IObserverOptions { forwardRef?: boolean // 是否透传 ref scheduler?: (updater: () void) void // 调度器可手动控制更新时机 displayName?: string // 包装后组件的 displayName } interface observerT extends React.FC { (component: T, options?: IObserverOptions): T }这三个选项的含义与源码实现一一对应见 packages/reactive-react/src/observer.tsforwardRef默认false当为true时内部通过forwardRef包装组件并把ref合并进 props 传入原组件const wrappedComponent realOptions.forwardRef ? forwardRef((props: any, ref: any) { return useObserver(() component({ ...props, ref }), realOptions) }) : (props: any) { return useObserver(() component(props), realOptions) }从返回值类型看开启forwardRef后返回组件的 props 会追加ref类型P { ref?: ... }方便父组件拿到子组件实例或 DOM 节点。scheduler更新调度器。默认情况下依赖变化会立即触发forceUpdate传入scheduler后由你决定何时执行updater例如节流、合并到帧回调、延迟到特定时机。其底层实现在 useObserver.tsnew Tracker(() { if (typeof options?.scheduler function) { options.scheduler(forceUpdate) } else { forceUpdate() } }, options?.displayName)displayName为包装后的组件设置displayName便于 React DevTools 调试定位。源码中在完成memo包装后会执行memoComponent.displayName realOptions.displayName。包装流程memo 静态属性提升从 observer.ts 的实现看observer返回的组件实际经历了三道加工按forwardRef选项生成包装函数核心逻辑都是调用useObserver(() component(props))用React.memo包裹使组件在 props 未变化时跳过无谓渲染调用hoistNonReactStatics将原组件的静态属性如propTypes、自定义静态方法提升到包装组件上避免memo包装导致静态成员丢失。这一过程保证了被包装组件既能响应式重渲染又不破坏原有组件 API 与静态约定。完整示例输入框与文本的双向联动文档示例直接展示了observer的最小可用场景——一个受响应式对象驱动的输入框与文本展示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 ) })运行逻辑拆解observable({ value: Hello world })创建一个响应式对象obs组件首次渲染时useObserver启动Tracker执行视图函数渲染过程中读取了obs.value于是该字段被登记为依赖用户在输入框键入时onChange修改obs.valueformily/reactive的响应式系统检测到依赖变更回调Tracker的调度器进而触发forceUpdate重渲染重渲染时会重新收集依赖若组件分支变化导致不再读取某个字段旧依赖会自动释放。也就是说依赖是「每次渲染动态重算」的而不是一次性绑定这与 Formily 表单中字段动态显隐、条件联动等场景天然契合。Observer函数渲染插槽实现局部精确渲染概念Observer组件被文档描述为「类似 Vue 的响应式插槽render slot」。它接收一个函数类型的childrenRender Props只要该函数内部消费了任何响应式数据数据变化时该函数就会自动重新执行渲染从而实现局部精确渲染——把渲染范围收缩到最小的 UI 片段避免整个组件树无谓刷新。其类型定义interface IObserverProps { children?: () React.ReactElement } type Observer React.FCReact.PropsWithChildrenIObserverProps注意children是可选的不传时Observer渲染为空片段Fragment传函数时执行函数得到元素。从 observer.ts 末尾可以看到Observer本身正是用observer实现的export const Observer observer((props: IObserverProps) { const children typeof props.children function ? props.children() : props.children return React.createElement(Fragment, {}, children) })这意味着Observer自身也是一个响应式函数组件每次渲染时它执行children()期间读取的响应式数据成为依赖依赖变化后只有Observer内部的这段渲染函数被重跑。与 observer 的分工组件级 vs 片段级observer包装的是整个函数组件粒度是「组件」Observer作用于组件内部的某一段 JSX粒度是「渲染片段」当组件很大、只有一小块区域依赖响应式数据时用Observer包裹该区域即可避免整组件重渲染这与 Reactmemo的优化思路互补memo拦 props 变化Observer拦响应式数据变化。完整示例输入框与文本的局部隔离文档示例将「输入框」与「文本」分别用两个Observer包裹各自独立追踪依赖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版本的差异值得体会外层函数组件本身不是响应式的它只在挂载时渲染一次输入框的value由第一个Observer渲染文本内容由第二个Observer渲染二者各自维护自己的依赖集合输入时obs.value变更两个Observer都会重跑自己的渲染函数但外层组件不会整体重渲染如果后续有第三块完全不读响应式数据的静态 UI它将完全不受数据变化影响这就是「局部精确渲染」的收益。深入原理Tracker 如何收集与触发依赖observer与Observer的响应式能力最终都汇聚到 useObserver.ts 中的一行return tracker.track(view)这里tracker是formily/reactive的Tracker实例其核心逻辑在 packages/reactive/src/tracker.tstrack: Reaction (tracker: Reaction) { if (!isFn(tracker)) return this.results if (this.track._boundary 0) return if (ReactionStack.indexOf(this.track) -1) { releaseBindingReactions(this.track) try { batchStart() ReactionStack.push(this.track) this.results tracker() } finally { ReactionStack.pop() this.track._boundary batchEnd() this.track._boundary 0 } } return this.results }关键点执行视图函数前先把当前 Reaction 推入ReactionStack读取响应式字段时reactive 的拦截器会将该字段绑定到栈顶 Reaction完成依赖收集每次track前会先releaseBindingReactions释放上一次收集的依赖保证依赖集合始终是「本次渲染实际读取的字段」这正是每次重渲染重新收集依赖的实现基础batchStart/batchEnd包裹执行过程使得一次渲染中多次字段读取/修改可以被批量处理避免渲染抖动。当依赖字段被修改时Reaction 的调度器被触发在useObserver中调度器默认调用forceUpdate来自 useForceUpdate.ts其内部维护了一个全局渲染队列RENDER_QUEUE渲染过程中产生的更新先入队待本轮渲染结束后统一消费从而避免渲染期内的 setState 竞态。StrictMode / ConcurrentMode 下的内存安全React 18 的StrictMode与ConcurrentMode下组件卸载UnMount可能不会按预期触发若依赖的Tracker实例得不到释放会造成内存泄漏。为此 useCompatFactory.ts 引入了自研垃圾回收机制通过useRef只创建一次Tracker实例借助一个由 React 持有的对象useState初始化的哨兵对象与 GarbageCollector 关联现代浏览器优先使用FinalizationRegistry在目标对象被 GC 时自动执行清理回调回收 Tracker不支持FinalizationRegistry的环境则退化为 10 秒定时器兜底回收在真实卸载useCompatEffect的清理函数时显式调用dispose()关闭。这套机制保证了即便在 StrictMode 双渲染、并发渲染等复杂场景下组件卸载后其响应式依赖也能被及时回收不产生悬挂的更新回调。使用建议与注意事项综合文档与源码给出以下实践建议优先函数组件observer只支持函数组件类组件请改用Observer包裹内部渲染片段或升级为函数组件。按需选择粒度整页/整卡依赖响应式数据用observer仅局部片段依赖时用Observer缩小渲染范围配合React.memo可最大化渲染性能。谨慎使用scheduler需要合并高频更新如拖拽、连续输入时可自定义scheduler将updater延迟到合适时机但必须保证最终会调用updater否则视图将停留在旧状态。注意forwardRef的类型变化开启后返回组件的 props 类型会追加refTypeScript 下调用方与实现方需保持一致。调试时设置displayName为包装组件命名避免 DevTools 中堆叠无意义的Observer/memo匿名组件便于定位渲染来源。依赖是动态重收集的不要在渲染函数外缓存对响应式数据的读取结果条件渲染下的分支字段应在各自分支内读取才能保证显隐切换时依赖正确切换。小结observer与Observer是 Formily 响应式体系接入 React 渲染层的两大入口前者把整个函数组件变成自动依赖追踪的 Reaction后者把依赖追踪精确到组件内部的渲染片段。它们的底层都依赖formily/reactive的Tracker依赖收集机制见 tracker.ts并通过 useForceUpdate.ts 的渲染队列与 gc.ts 的垃圾回收保证了在 React 18 StrictMode / 并发渲染下的正确性与内存安全。掌握这两个 API你就掌握了 Formily 表单高性能渲染与精确联动的底层开关。赞分享前端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 响应式渲染formily/reactive-react 的 observer 与 Observer 使用指南Formily 响应式渲染formily/reactive react 的 observer 与 Observer 使用指南 本文是 Formily 官方文前端UI组件formily/reactive-react 使用指南用 observer 让 React 组件与响应式状态零成本联动formily/reactive react 使用指南用 observer 让 React 组件与响应式状态零成本联动 formily/reactive前端UI组件Formily 响应式渲染指南深入理解 observer HOC 与 Observer 组件Formily 响应式渲染指南深入理解 observer HOC 与 Observer 组件 导读 本文以 Formily 官方文档 observer.md前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表