ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入解析 Angular CDK 私有包 @angular/cdk/observers/private:SharedResizeObserver 共享 ResizeObserver 机制

深入解析 Angular CDK 私有包 @angular/cdk/observers/private:SharedResizeObserver 共享 ResizeObserver 机制 深入解析 Angular CDK 私有包 angular/cdk/observers/privateSharedResizeObserver 共享 ResizeObserver 机制【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components导读在 Angular Material 与 Angular CDK 的源码仓库中angular/cdk/observers/private是一个面向 Angular 组件库内部使用的私有 API 包其核心导出是SharedResizeObserver—— 一个基于浏览器原生ResizeObserver、以按 box 类型共享一个观察器实例为设计思想的响应式尺寸监听服务。本文以该包的 API 报告 为骨架结合 shared-resize-observer.ts 源码与其 单元测试 实现完整讲解其公开 API 形态、内部共享机制、RxJS 流式设计、生命周期管理以及它在 Material 组件中的真实消费场景帮助读者理解 Angular 组件库在尺寸监听上的工程化实践。一、API 报告总览包的公开契约goldens/cdk/observers/private/index.api.md是由 API Extractor 自动生成的 API 报告文件文件头部明确标注API Report File for angular/cdk_observers_private且注明不要编辑该文件它由 API Extractor 生成。这类 golden 文件在 Angular 仓库中用于锁定公开 API 形态任何对导出签名、可见性标记的变更都会在 CI 中触发校验仓库内的 goldens 目录下所有index.api.md均承担此职责。从报告可以看出该包的全部公开面仅包含一个类依赖关系极简公开符号类型可见性SharedResizeObserverclass实现OnDestroypublic依赖angular/coreOnDestroy、i0工厂声明、rxjsObservable内部依赖报告的 TypeScript 声明部分完整还原了类签名import * as i0 from angular/core; import { Observable } from rxjs; import { OnDestroy } from angular/core; export class SharedResizeObserver implements OnDestroy { constructor(); ngOnDestroy(): void; observe(target: Element, options?: ResizeObserverOptions): ObservableResizeObserverEntry[]; static ɵfac: i0.ɵɵFactoryDeclarationSharedResizeObserver, never; static ɵprov: i0.ɵɵInjectableDeclarationany; }这里的ɵfacfactory declaration与ɵprovinjectable declaration是 Angular 编译器生成的元数据ɵprov表明SharedResizeObserver是一个可注入的injectable服务可被 Angular 依赖注入系统直接提供与消费。该类的公开行为被刻意收敛为两个方法observe()与ngOnDestroy()配合无参构造函数构成了一个创建即就绪、按需 observe、随注入容器销毁的极简服务契约。二、从声明到实现SharedResizeObserver 的源码级剖析2.1 按 box 类型分组的观察器映射SharedResizeObserver的完整实现位于 shared-resize-observer.ts。类的核心状态是一个以ResizeObserverBoxOptions即content-box、border-box、device-pixel-content-box为键的 Mapprivate _observers new MapResizeObserverBoxOptions, SingleBoxSharedResizeObserver();observe(target, options?)的逻辑因此非常直接observe(target: Element, options?: ResizeObserverOptions): ObservableResizeObserverEntry[] { const box options?.box || content-box; if (!this._observers.has(box)) { this._observers.set(box, new SingleBoxSharedResizeObserver(box)); } return this._observers.get(box)!.observe(target); }要点有三默认 box 为content-box未传options或options.box为空时统一按内容盒监听按 box 惰性建观察器只有首次以某 box 类型调用时才创建对应的底层ResizeObserver避免无谓开销同 box 同观察器所有相同 box 类型的元素共享同一个底层观察器实例。2.2 SingleBoxSharedResizeObserver真正的共享单元源码中的私有内部类SingleBoxSharedResizeObserver承载了实际共享逻辑。构造时它会判断运行环境是否支持ResizeObserverif (typeof ResizeObserver ! undefined) { this._resizeObserver new ResizeObserver(entries this._resizeSubject.next(entries)); }所有被观察元素产生的原生ResizeObserverEntry[]事件被统一汇入一个_resizeSubjectRxJSSubject然后在每个元素的专属 Observable 管道中做分流new ObservableResizeObserverEntry[](observer { const subscription this._resizeSubject.subscribe(observer); this._resizeObserver?.observe(target, {box: this._box}); return () { this._resizeObserver?.unobserve(target); subscription.unsubscribe(); this._elementObservables.delete(target); }; }).pipe( filter(entries entries.some(entry entry.target target)), shareReplay({bufferSize: 1, refCount: true}), takeUntil(this._destroyed), )这段管道设计值得逐项拆解filter(...)分流从共享事件流中只保留包含目标元素entry.target target的事件实现一个观察器、多个元素互不干扰shareReplay({bufferSize: 1, refCount: true})回放最近一次事件保证后订阅的消费者能立刻拿到初始尺寸信息refCount: true开启引用计数当该元素的所有订阅都取消后Observable 自动结束进而触发 unsubscribe 清理逻辑unobserve 从 Map 删除实现无人订阅即自动解除观察takeUntil(this._destroyed)观察器整体销毁时统一终止所有元素流。2.3 缓存去重同一元素同一 box 只建立一条流observe内部用_elementObservablesMapElement, ObservableResizeObserverEntry[]缓存元素到流的映射observe(target: Element): ObservableResizeObserverEntry[] { if (!this._elementObservables.has(target)) { // 创建并缓存新流 } return this._elementObservables.get(target)!; }这意味着重复调用observe返回的是同一个 Observable 实例这一行为被单元测试显式锁定见 shared-resize-observer.spec.ts 中should return the same observable for the same element and same box同时也是共享语义的核心订阅方之间天然共享事件源。2.4 生命周期与 Angular 集成SharedResizeObserver用Service()装饰器标记源码中为Service()Angular 源码内部即Injectable并注入了NgZone与RendererFactory2。其构造函数中在开发模式ngDevMode下通过runOutsideAngular在 window 上注册全局error监听专门捕获ResizeObserver loop limit exceeded错误const loopLimitExceededErrorHandler (e: unknown) { if (e instanceof ErrorEvent e.message ResizeObserver loop limit exceeded) { console.error( ${e.message}. This could indicate a performance issue with your app. ... ); } };由于 Chrome 控制台默认不展示这类错误该监听保证开发者能感知到因尺寸监听引发的性能问题监听器注册在 Angular zone 之外避免引入无谓的变更检测触发。ngOnDestroy()则负责遍历销毁所有SingleBoxSharedResizeObserver并解绑 error 监听与 API 报告 中的OnDestroy实现一一对应。三、为什么要共享设计动机与性能考量3.1 避免观察器实例爆炸源码注释明确引用了 WICG resize-observer 的 issue #59共享ResizeObserver实例是官方推荐的做法。如果每个需要监听尺寸的组件都new一个自己的ResizeObserver在表格、标签页、下拉等密集组件场景下会创建大量观察器浏览器需要同时对它们执行布局与回调调度开销显著上升。3.2 为什么按 box 类型分三个观察器SharedResizeObserver并没有只共享一个ResizeObserver而是为content-box、border-box、device-pixel-content-box各维护一个。类注释解释了原因避免后续以不同 box 类型调用observe时污染先前观察所派发的事件——同一ResizeObserver实例上混用多种 box 会导致事件行为不一致。以 box 为粒度隔离既保住共享收益又保证每种 box 语义的纯粹性。3.3 与默认 box 值的一致性默认content-box的选择与原生ResizeObserver.observe的默认行为一致符合所见即所得的直觉监听的是元素内容盒尺寸变化与 CSScontent-box的含义相同。四、测试锁定行为契约shared-resize-observer.spec.ts 用一套MockResizeObserver手动触发回调以消除浏览器时序抖动锁定了五条核心行为同元素同 box 返回同一 Observableobservable1 observable2不同元素返回不同 Observable事件流按元素隔离不同 box 返回不同 Observablebox 类型参与流缓存键引用计数语义当最后一个订阅取消后再次observe会得到新的 Observable旧流已因 refCount 归零而结束清理元素被unobserve订阅即得初始尺寸subscribe后立刻收到一次初始尺寸事件因为shareReplay(bufferSize: 1)回放了首次 observe 触发的回调同时只触发目标元素对应回调其他元素流不被通知。这些用例特别是第 4、5 条直观验证了前文对shareReplay({bufferSize: 1, refCount: true})语义的解读是理解该服务行为契约的最佳入口。从构建配置看private/BUILD.bazel 中该包仅依赖angular/core与rxjs外加内部//src:dev_mode_types印证了其极简、零额外依赖的设计测试目标unit_tests通过ng_web_test_suite在浏览器环境执行。五、实战消费Material 组件如何用它SharedResizeObserver虽然位于私有包入口导出见 private/index.ts但它是 Material 组件库内部的重要基础设施。从源码检索可见Material 表单域form-field的浮动标签直接消费了它。以 floating-label.ts 为例组件通过依赖注入获取服务private _resizeObserver inject(SharedResizeObserver);并在初始化时以border-box监听宿主元素尺寸this._resizeObserver .observe(this._elementRef.nativeElement, {box: border-box}) .subscribe(...);源码注释还原了真实的业务场景当输入框填充后标签会增长 1px 并触发ResizeObserver随后标签展开至全宽再次触发——连续的同帧回调很容易抛出让 Chrome 静默吞掉的ResizeObserver loop limit exceeded错误。正因为有SharedResizeObserver在 window 层面统一捕获并打印该错误开发者才能在控制台发现这类因尺寸监听触发的性能隐患同时共享观察器让form-field、slider、tabs见 slider.ts、paginated-tab-header.ts 等文件等组件在大量实例并存时依然只维护少量底层ResizeObserver。六、使用要点速查关注点结论依据默认 boxcontent-boxshared-resize-observer.tsobserve()支持的 boxcontent-box/border-box/device-pixel-content-box源码SingleBoxSharedResizeObserver类注释同元素重复 observe返回同一 Observable缓存复用单元测试should return the same observable...取消订阅最后一个订阅取消后自动unobserve并清理缓存shareReplay({refCount: true}) 测试用例订阅时机订阅即回放最近一次尺寸事件可立即读到初始尺寸shareReplay({bufferSize: 1}) 测试用例错误处理window 级监听ResizeObserver loop limit exceeded并打印性能告警构造函数loopLimitExceededErrorHandler环境降级浏览器不支持ResizeObserver时静默退化为空流_resizeObserver为 undefined构造函数typeof ResizeObserver ! undefined分支集成要求通过 Angular 依赖注入获取随注入器生命周期销毁Service()与ngOnDestroy()使用提示该包路径为angular/cdk/observers/private其存在意义是作为 Angular 组件库内部共享设施而非面向应用开发者的公共 APIcdk/observers的公开入口 public-api.ts 仅导出cdkObserveContent指令SharedResizeObserver并未进入公开 API。应用开发者若需要尺寸监听能力应优先使用公开的cdkObserveContent用法示例见 observers.md或自行基于原生ResizeObserver实现而若要理解 Angular 组件库如何在性能共享与语义隔离之间取得平衡SharedResizeObserver这份源码则是一份高质量的参考范本。结语从一份仅有一个公开类的 API 报告 出发结合源码与测试可以还原出SharedResizeObserver完整的工程画像按 box 粒度共享底层ResizeObserver、以filter shareReplay(refCount)的 RxJS 管道实现元素级事件分流与自动清理、在 Angular zone 之外接管 loop 错误告警并通过 golden API 报告与单元测试将这套契约牢牢锁定。它既是 Angular Material 诸多尺寸敏感组件表单域浮动标签、滑块、标签页等背后的性能基石也为读者提供了一个如何在组件库内设计高性能、可测试的浏览器 API 封装的极佳学习样本。【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表