首页/新闻资讯/正文详情

NgRx SignalStore 状态追踪实战:从 `getState` + `effect` 到同步的 `watchState`

发布时间:2026/9/27 1:35:16 来源:云帆数科 栏目:资讯中心
NgRx SignalStore 状态追踪实战:从 `getState` + `effect` 到同步的 `watchState`
前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载状态追踪是 SignalStore 实现自定义扩展功能的基石例如日志记录、状态撤销/重做undo/redo与存储同步如 localStorage 持久化。本文以 state-tracking.md 文档为主体结合ngrx/signals的 state-source.ts 源码与 state-source.spec.ts 测试用例讲解两种追踪方式的差异与取舍帮助你掌握getState与watchState的完整用法并能基于它们实现自定义 SignalStore 特性。为什么需要状态追踪SignalStore 的每一个状态切片都对应一个独立的 signal视图可以直接读取store.count()这样的状态信号。但当你需要观察整体状态的变化——例如把每次变更后的完整 state 快照记录下来时就需要一种能够感知任意状态切片变化的机制。在ngrx/signals中这个能力由 state-source.ts 中的getState与watchState两个函数提供它们都从 index.ts 对外导出统一适用于 SignalStore 与signalState两种状态源。方式一getState搭配effectgetState用于读取 SignalStore或signalState的当前状态快照。它的特殊之处在于当在一个响应式上下文如effect、computed中被调用时内部读取的每一个状态 signal 都会被自动追踪状态变化后调用方会自动重新执行。从 state-source.ts 的源码可以看出其实现原理它遍历存储在STATE_SOURCE符号上的全部状态信号逐个读取并聚合成一个新的 state 对象export function getStateState extends object( stateSource: StateSourceState ): State { const signals: Recordstring | symbol, Signalunknown stateSource[STATE_SOURCE]; return Reflect.ownKeys(stateSource[STATE_SOURCE]).reduce((state, key) { const value signals[key](); return { ...state, [key]: value }; }, {} as State); }因此getState并不是一个一次性快照工具而是能够参与到响应式追踪中的读取函数。基础示例effect 中追踪状态变化下面是一个计数器 Store通过withHooks的onInit钩子在effect内调用getState实现状态一变就打印import { effect } from angular/core; import { getState, patchState, signalStore, withHooks, withMethods, withState, } from ngrx/signals; export const CounterStore signalStore( withState({ count: 0 }), withMethods((store) ({ increment(): void { patchState(store, { count: store.count() 1 }); }, })), withHooks({ onInit(store) { effect(() { // The effect is re-executed on state change. const state getState(store); console.log(counter state, state); }); setInterval(() store.increment(), 1_000); }, }) );每隔 1 秒调用一次incrementeffect 就会带着最新的 state 重新执行。effect 的无闪烁glitch-free合并行为受effect本身 glitch-free 特性的影响如果同一个 tick 内状态被多次修改effect 只会以最终状态执行一次。例如连续两次调用increment你只会看到一次日志内容是最终值。这种异步合并对性能是友好的但对某些功能却是障碍。文档原文明确指出像状态 undo/redo 这样的功能需要记录 SignalStore 的全部状态变化而不能把同一 tick 内的多次更新合并掉。这就是watchState存在的意义。方式二watchState同步追踪每一次变化watchState允许同步地追踪 SignalStore 的状态变化。它接收两个参数第一个参数SignalStore或signalState实例第二个参数watcher 回调函数在每次状态变化后执行。默认情况下watchState必须在注入上下文injection context中调用并绑定其生命周期——当所在 injector 被销毁时watcher 自动清理。基础示例effect 与 watchState 的行为对比import { effect } from angular/core; import { getState, patchState, signalStore, watchState, withHooks, withState, } from ngrx/signals; export const CounterStore signalStore( withState({ count: 0 }), withMethods((store) ({ increment(): void { patchState(store, { count: store.count() 1 }); }, })), withHooks({ onInit(store) { watchState(store, (state) { console.log([watchState] counter state, state); }); // logs: { count: 0 }, { count: 1 }, { count: 2 } effect(() { console.log([effect] counter state, getState(store)); }); // logs: { count: 2 } store.increment(); store.increment(); }, }) );在这个例子中store.increment()被连续调用两次watchState的 watcher 会被执行3 次一次携带初始状态{ count: 0 }随后每次 increment 各一次{ count: 1 }、{ count: 2 }effect只会执行1 次且携带最终状态{ count: 2 }。也就是说watchState有两大特点同步执行状态一改变watcher 立刻运行不做同 tick 合并初始即执行注册 watcher 后会立刻以当前状态调用一次这正是上面出现{ count: 0 }的原因。源码视角watchState 是如何做到逐个通知的从 state-source.ts 可以看到watchState的实现骨架export function watchStateState extends object( stateSource: StateSourceState, watcher: StateWatcherState, config?: { injector?: Injector } ): { destroy(): void } { if (typeof ngDevMode ! undefined ngDevMode !config?.injector) { assertInInjectionContext(watchState); } const injector config?.injector ?? inject(Injector); const destroyRef injector.get(DestroyRef); addWatcher(stateSource, watcher); executeWatcher(stateSource, watcher); const destroy () removeWatcher(stateSource, watcher); destroyRef.onDestroy(destroy); return { destroy }; }关键机制对应如下注入上下文校验当没有显式传入injector时开发模式下会调用assertInInjectionContext断言脱离注入上下文调用会抛出NG0203: watchState() can only be used within an injection context错误自动清理从当前或传入的injector 中取得DestroyRef在onDestroy中自动移除 watcher注册即执行addWatcher之后立即调用executeWatcher所以第一次回调携带的是初始状态同步通知patchState在更新状态信号的末尾会调用notifyWatchers遍历并同步执行所有 watcher因此同 tick 内的多次patchState会触发多次回调不会被合并。值得注意的实现细节是executeWatcher使用了untracked包裹state-source.tsfunction executeWatcherState extends object( stateSource: StateSourceState, stateWatcher: StateWatcherState ): void { untracked(() { const state getState(stateSource); stateWatcher(state); }); }这意味着 watcher 内部读取的任何 signal不会泄漏到外层响应式上下文。对应的测试用例state-source.spec.ts专门验证了当patchState由某个effect触发时watcher 中读取其他 signal 不会让该 effect 额外重跑。手动清理调用destroywatchState返回一个包含destroy方法的对象。如果需要在 injector 销毁之前提前停止观察手动调用destroy即可import { patchState, signalStore, watchState, withHooks, witMethods, withState, } from ngrx/signals; export const CounterStore signalStore( withState({ count: 0 }), withMethods((store) ({ increment(): void { patchState(store, { count: store.count() 1 }); }, })), withHooks({ onInit(store) { const { destroy } watchState(store, console.log); setInterval(() store.increment(), 1_000); // Stop watching after 5 seconds. setTimeout(() destroy(), 5_000); }, }) );这里watchState(store, console.log)的返回值被解构出destroy在 5 秒后调用以终止观察此后状态再变化也不会触发 watcher。从源码看destroy的本质是调用removeWatcher把该 watcher 从STATE_WATCHERS一个以状态源为 key 的WeakMap中过滤掉state-source.ts。在注入上下文之外使用传入injectorwatchState也可以在注入上下文之外使用方法是把injector作为第三个参数config 对象传入此时生命周期绑定到该 injectorimport { Component, inject, Injector, OnInit } from angular/core; import { watchState } from ngrx/signals; import { CounterStore } from ./counter-store; Component({ /* ... */ providers: [CounterStore], }) export class Counter implements OnInit { readonly #injector inject(Injector); readonly store inject(CounterStore); ngOnInit(): void { watchState(this.store, console.log, { injector: this.#injector, }); setInterval(() this.store.increment(), 2_000); } }在这个组件示例中watcher 的生命周期与组件的 injector 绑定——组件销毁时 watcher 自动清理无需手动调用destroy。源码中config?.injector ?? inject(Injector)state-source.ts正是这条分支的实现。测试用例佐证三种清理路径都被覆盖state-source.spec.ts 为watchState提供了一套完整的验证可以直接作为你理解其行为的参考场景测试要点位置初始即执行注册后立即收到初始状态0随后patchState三次各收到1、2、3state-source.spec.tsinjector 销毁自动清理服务销毁后不再收到后续更新state-source.spec.ts手动destroy清理调用destroy后 watcher 停止state-source.spec.ts传入 injector 的清理多个 injector 各自独立销毁、互不影响state-source.spec.ts脱离注入上下文报错抛出NG0203错误state-source.spec.ts响应式上下文隔离watcher 内读取 signal 不泄漏到触发方 effectstate-source.spec.tsStore 内外均可使用既能在withHooks.onInit中使用也能在组件中注入后使用state-source.spec.ts另外测试还验证了watchState对signalState同样有效因为signalState与 SignalStore 共享同一个STATE_SOURCE机制见 signal-state.ts。两种追踪方式如何选择维度getStateeffectwatchState执行时机异步、延迟到变更通知后同步、状态更新立即触发同 tick 多次变更合并为一次只取最终值每次都触发不合并初始状态需要显式读取注册后立即回调一次适用场景日志、派生副作用、UI 联动undo/redo、状态快照、存储同步生命周期由effect所在上下文管理绑定 injector 的DestroyRef或手动destroy文档开头即点明了状态追踪的典型应用方向日志记录logging、状态撤销/重做undo/redo、存储同步storage synchronization。结合watchState的同步、逐次、不合并特性你可以实现 undo/redowatcher 中把每次状态快照 push 进历史栈撤销时从栈中弹出上一个快照并用patchState恢复再配合destroy在必要时停止记录实现存储同步watcher 中把最新状态写入localStorage/sessionStorageStore 初始化时再从存储中读取并patchState实现日志/审计用getStateeffect做轻量级的变更日志即可满足大多数需求。小结状态追踪是搭建自定义 SignalStore 特性的底层能力getState负责在响应式上下文中读取整体状态并参与自动追踪watchState则提供同步、逐次、自动绑定生命周期的状态变更回调。两者由 state-source.ts 统一实现并得到了 state-source.spec.ts 的完整验证。选择哪种方式取决于你的场景能否容忍同 tick 内的状态合并——需要完整变化轨迹时watchState是明确答案。赞分享前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载相关推荐NgRx SignalStore 完整实战指南用 Signals 构建可扩展的 Angular 状态管理NgRx SignalStore 完整实战指南用 Signals 构建可扩展的 Angular 状态管理 NgRx SignalStore 是 NgRx 提供前端状态管理NgRx SignalStore Events 插件实战基于事件驱动的响应式状态管理指南NgRx SignalStore Events 插件实战基于事件驱动的响应式状态管理指南 Events 插件为 NgRx SignalStore 引入了一层基前端状态管理NgRx ESLint 规则实战with-state-no-arrays-at-root-level 与 SignalStore 状态根级约束NgRx ESLint 规则实战 with state no arrays at root level 与 SignalStore 状态根级约束 导读 本文围前端状态管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

AD24原理图网格设置全解析:Snap/Visible/Electrical三系统避坑指南
AD24原理图网格设置全解析:Snap/Visible/Electrical三系统避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:35:09

《小型支付商城系统》四色建模设计实战:从用例图到 DDD 领域拆分的研发设计全流程
《小型支付商城系统》四色建模设计实战:从用例图到 DDD 领域拆分的研发设计全流程

文档教程后端 【免费下载链接】CodeGuide :books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、… · 2026/9/27 1:35:09

基于RAG的电商店铺运营助手系统设计毕业设计源码(源码+lw+部署文档+讲解等)
基于RAG的电商店铺运营助手系统设计毕业设计源码(源码+lw+部署文档+讲解等)

博主介绍:✌ 专注于VUE,小程序,安卓,Java,python,物联网专业,有18年开发经验,长年从事毕业指导,项目实战✌选取一个适合的毕业设计题目很重要。✌关注✌私信我✌具体的问题,我会尽力帮助你。一、… · 2026/9/27 1:35:03

树莓派离线语音助手实战:基于sherpa-onnx从零搭建
树莓派离线语音助手实战:基于sherpa-onnx从零搭建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 3:35:56

Google Antigravity SDK Agent Skills指南:用文件系统SKILL.md让AI Agent掌握领域知识
Google Antigravity SDK Agent Skills指南:用文件系统SKILL.md让AI Agent掌握领域知识

Google Antigravity SDK Agent Skills指南:用文件系统SKILL.md让AI Agent掌握领域知识 【免费下载链接】antigravity-sdk-python A Python library for building AI agents that leverage the full power of Google Antigravity. 项目地址: https://gitcode.com/g… · 2026/9/27 3:35:56

视频解析与视频提取实战指南:从原理、工具到避坑经验
视频解析与视频提取实战指南:从原理、工具到避坑经验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 3:35:50

配电变压器检测数据集:VOC+YOLO双格式工程实践指南
配电变压器检测数据集:VOC+YOLO双格式工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 3:35:50

从场景反推边缘AI芯片选型:算力指标、隐形天花板与验证流程
从场景反推边缘AI芯片选型:算力指标、隐形天花板与验证流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 3:35:38

《HarmonyOS 7 精准碰一碰跨设备协作开发实战》07:异常恢复、状态机与CrossDrop工程收尾【鸿蒙心迹】
《HarmonyOS 7 精准碰一碰跨设备协作开发实战》07:异常恢复、状态机与CrossDrop工程收尾【鸿蒙心迹】

前六篇我们一路做下来,功能都有了。但真用起来呢?传到一半App退了怎么办?窗口关了怎么办?URI失效了怎么办?这一篇把这些问题收口。写到这里,CrossDrop已经有了:精准分享、触点识别、区域路由、多… · 2026/9/27 3:35:32

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码