ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

NgRx ComponentStore 状态更新详解:updater、setState 与 patchState 的源码级实战指南

NgRx ComponentStore 状态更新详解:updater、setState 与 patchState 的源码级实战指南 前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载本文基于 NgRx Platform 仓库中 ComponentStore 指南的 Updating state 章节展开系统讲解组件级响应式状态存储ngrx/component-store的三种状态更新方式——updater、setState与patchState的用法、参数约束与底层实现。读完本文你将掌握如何在 Angular 应用中编写符合不可变原则的 Store 服务并理解状态未初始化时的报错机制、queueScheduler的异步调度细节以及 updater 返回订阅用于取消更新的源码原理。背景ComponentStore 的定位ngrx/component-store是 NgRx 生态中面向组件/服务级本地状态的响应式存储库。在当前仓库中package.json 显示其版本为22.0.1对等的依赖要求为angular/core ^22.0.0与rxjs ^6.5.3 || ^7.5.0。需要特别说明的一点原文档中的官方提示NgRx Signals 已成为新的默认推荐。NgRx 团队建议在 Angular 本地状态管理中使用ngrx/signals库ComponentStore 仍受支持但官方鼓励新项目使用ngrx/signals存量项目可以考虑迁移。本文聚焦于仍在使用 ComponentStore 的项目如何正确地写入状态。状态更新是 ComponentStore 的核心操作。按官方指南的总结ComponentStore可以在以下三种方式下更新状态调用setState调用patchState创建updater并传入输入值三种方式都最终汇入同一条内部链路component-store.ts 中以ReplaySubject为核心的stateSubject$。下面逐一深入。updater方法描述状态如何变化updater方法描述的是状态如何变化HOW。它接收一个纯函数函数参数为当前状态和一个输入值要求不可变地返回新状态。一个 ComponentStore 中可以有多个 updater它们类似于ngrx/storereducer 中的 CASE 语句或on()函数。官方文档给出的核心价值主张是使用updater可以把业务逻辑从组件中抽离到服务里让组件更易读、更易测试。基本用法以下示例定义了电影列表状态并创建一个追加电影的 updatermovies.store.tsInjectable() export class MoviesStore extends ComponentStoreMoviesState { constructor() { super({ movies: [] }); } readonly addMovie this.updater((state, movie: Movie) ({ movies: [...state.movies, movie], })); }updater返回一个可调用函数既可以命令式地传入具体值也可以接收一个 Observablemovies-page.component.tsComponent({ template: button (click)add(New Movie)Add a Movie/button , providers: [MoviesStore], }) export class MoviesPageComponent { constructor(private readonly moviesStore: MoviesStore) {} add(movie: string) { this.moviesStore.addMovie({ name: movie, id: generateId() }); } }源码解析updater 的完整调用链updater的实现位于 component-store.ts其内部管道值得逐段理解const observable$ isObservable(observableOrValue) ? observableOrValue : of(observableOrValue); const subscription observable$ .pipe( // Push the value into queueScheduler observeOn(queueScheduler), // If the state is not initialized yet, well throw an error. tap(() this.assertStateIsInitialized()), withLatestFrom(this.stateSubject$), map(([value, currentState]) updaterFn(currentState, value!)), tap((newState) this.stateSubject$.next(newState)), catchError((error: unknown) { if (isSyncUpdate) { syncError error; return EMPTY; } return throwError(error); }), takeUntil(this.destroy$) ) .subscribe();关键设计点有四值统一转为流传入的普通值会被of()包装为 Observable因此命令式调用与响应式调用走同一条管道代码只需处理一种形态。observeOn(queueScheduler)的异步化更新值被推入queueScheduler即状态更新发生在当前的宏任务、但晚于同步代码的时刻。这是命令式调用却能立刻读到新状态与避免在管道执行中途产生中间状态的平衡设计测试 component-store.spec.ts 专门验证了初始化与更新均通过 queueScheduler 调度时不会抛错。同步错误可捕获catchableisSyncUpdate标志配合catchError使得同步更新中抛出的错误包括初始化断言错误能被调用方的try/catch捕获而不是变成未处理的异步异常。测试 component-store.spec.ts 分别覆盖了 updater、setState回调、patchState回调中同步抛错均能被同步捕获的场景。返回Subscription调用 updater 传入 Observable 时会返回订阅对象调用unsubscribe()即可取消该次更新流而互不影响其他更新流。测试 component-store.spec.ts 用interval/timer模拟了并发两个 Observable 更新流验证取消第一个后第二个仍持续发射。updater的泛型签名component-store.ts还做了类型层面的推导若updaterFn的第二参数无类型void返回的函数不需要入参否则返回一个接收ValueType | ObservableValueType的函数——这也正是updater 既能传值也能传 Observable的类型来源。setState方法整状态重置与惰性初始化setState有两种调用形态传入状态对象将整个状态重置为给定值。这也是**惰性初始化lazy initialization**的执行方式传入回调函数允许开发者部分地变更状态回调基于当前状态返回新状态。官方示例movies-page.component.tsComponent({ template: ..., providers: [ComponentStore], }) export class MoviesPageComponent implements OnInit { constructor( private readonly componentStore: ComponentStoreMoviesState ) {} ngOnInit() { this.componentStore.setState({ movies: [] }); } resetMovies() { // resets the State to empty array this.componentStore.setState({ movies: [] }); } addMovie(movie: Movie) { this.componentStore.setState((state) { return { ...state, movies: [...state.movies, movie], }; }); } }源码解析对象与回调的两条路径setState的实现非常简短component-store.tssetState(stateOrUpdaterFn: T | ((state: T) T)): void { if (typeof stateOrUpdaterFn ! function) { this.initState(stateOrUpdaterFn); } else { this.updater(stateOrUpdaterFn as (state: T) T)(); } }对象路径走initStatecomponent-store.tsprivate initState(state: T): void { scheduled([state], queueScheduler).subscribe((s) { this.isInitialized true; this.stateSubject$.next(s); }); }注意initState同样经由scheduled([state], queueScheduler)发布即构造器初始化与 setState 初始化的生效时机都是异步排队的isInitialized标志正是在这里被置为true。这也解释了测试中在queueScheduler中创建 Store 并紧接着patchState不会报错的用例——两者都在同一队列中按序执行。回调路径直接复用updater机制并立即调用() 无值入参。因此回调式setState在状态未初始化时会同步抛错这一点由测试 component-store.spec.ts 明确验证expect(() { componentStore.setState(() ({ setState: new state })); }).toThrow( new Error( ComponentStore has not been initialized yet. Please make sure it is initialized before updating/getting. ) );惰性初始化的实践意义配合 initialization.md 的说明如果开发者不希望 selector 在任何有意义的状态就绪前就返回值可以不在构造器中传入初始状态而在数据就绪后调用setState传入完整状态完成惰性初始化同一方式也用于重置状态reset。patchState方法部分状态合并patchState接收三种输入之一部分状态对象PartialT部分状态流ObservablePartialT部分更新回调(state: T) PartialT传入部分状态时它会用给定值合并patch现有状态传入部分更新函数时则用回调返回值合并。官方示例interface MoviesState { movies: Movie[]; selectedMovieId: string | null; } Component({ template: ..., providers: [ComponentStore], }) export class MoviesPageComponent implements OnInit { constructor( private readonly componentStore: ComponentStoreMoviesState ) {} ngOnInit() { this.componentStore.setState({ movies: [], selectedMovieId: null, }); } updateSelectedMovie(selectedMovieId: string) { this.componentStore.patchState({ selectedMovieId }); } addMovie(movie: Movie) { this.componentStore.patchState((state) ({ movies: [...state.movies, movie], })); } }注意原文档强调必须在任何patchState调用之前完成状态初始化否则将抛出 not initialized 错误。源码解析patchState 是 updater 的组合patchState的完整实现component-store.ts只有十几行patchState( partialStateOrUpdaterFn: | PartialT | ObservablePartialT | ((state: T) PartialT) ): void { const patchedState typeof partialStateOrUpdaterFn function ? partialStateOrUpdaterFn(this.get()) : partialStateOrUpdaterFn; this.updater((state, partialState: PartialT) ({ ...state, ...partialState, }))(patchedState); }两个细节值得注意回调输入会被立即求值若传入函数patchState会先用this.get()读取当前状态并同步执行回调得到PartialT再交给 updater。get()内部调用assertStateIsInitialized()component-store.ts所以未初始化时错误在调用点同步抛出。合并是浅层展开内部 updater 执行{ ...state, ...partialState }顶层属性被覆盖、其余属性保留并非深层合并——嵌套对象如value2: { foo: bar }会整体替换。测试 component-store.spec.ts 覆盖了三种输入形态的行为包括传入 Observable 时逐个值依次合并、以及基于前态回调的部分更新。未初始化调用patchState的三种形态对象 / Observable / 回调均会抛错分别由测试 component-store.spec.ts 覆盖。初始化断言的统一出口三种更新方式最终都经过assertStateIsInitialized()component-store.tsprivate assertStateIsInitialized(): void { if (!this.isInitialized) { throw new Error( ${this.constructor.name} has not been initialized yet. Please make sure it is initialized before updating/getting. ); } }错误信息中包含具体类名如MoviesStore has not been initialized yet...便于在生产环境快速定位是哪个 Store 缺少初始化。另外若以异步 Observable在初始化前调用 updater订阅会在断言失败时关闭、状态不会更新测试见 component-store.spec.ts若该流在初始化完成后才发射值则正常更新component-store.spec.ts。三种方式的选择对比方式输入形态语义典型场景setState(对象)T整体重置 初始化首次初始化、重置全部状态setState(回调)(state: T) T基于前态的完整返回需要重写多个顶层字段updater(fn)(state, value) T入参可为值或 Observable声明式更新规则可复用业务规则抽入 Store 服务patchState(对象/流/回调)PartialT \| ObservablePartialT \| (state) PartialT浅合并部分状态更新单个/少数顶层字段共同约束都要求状态已初始化惰性初始化除外都经由queueScheduler异步生效都绑定takeUntil(this.destroy$)随 Store 销毁自动清理同步错误均可在调用方捕获。延伸阅读围绕本文主题仓库中还有以下可继续深入的文档与源码初始化构造器 / 惰性初始化initialization.md读取状态select、get、state$read.md副作用effect方法与 updater 互补effect.md生命周期钩子ngrxOnStoreInit/ngrxOnStateInit与provideComponentStorelifecycle_hooks.ts完整测试套件状态更新、取消、错误边界的行为验证component-store.spec.ts赞分享前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载相关推荐NgRx ComponentStore 全解析Angular 局部状态管理的理念、API 与源码实现NgRx ComponentStore 全解析Angular 局部状态管理的理念、API 与源码实现 本篇以 NgRx platform 仓库中 Compon前端状态管理NgRx Platform 中 ComponentStore 实战指南从应用状态分类到 Local UI State 管理NgRx Platform 中 ComponentStore 实战指南从应用状态分类到 Local UI State 管理 本文基于 NgRx Platfor前端状态管理NgRx Platform 中用 ComponentStore 读取状态select、selectSignal 与 state 信号完整实践指南NgRx Platform 中用 ComponentStore 读取状态select、selectSignal 与 state 信号完整实践指南 本篇指南聚焦前端状态管理上一篇革命性协作决策平台Loomio如何让组织决策更民主高效下一篇sish企业级应用多租户环境下的SSH隧道管理终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表