前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载本指南围绕 NgRx SignalStore 的withLinkedState特性展开它允许在 store 中定义依赖其他信号的状态切片并在依赖变化时自动同步更新。读完本文你将掌握隐式链接计算函数自动包装为linkedSignal与显式链接直接挂载WritableSignal两种用法理解其底层实现原理并能结合patchState与链式依赖写出符合实战要求的关联状态管理方案。withLinkedState 是什么withLinkedState是 NgRx SignalStore位于 modules/signals/src/with-linked-state.ts提供的 store 特性用于创建依赖其他信号的状态切片linked state slice。它与withState、withComputed、withMethods一样作为signalStore(...)的参数参与 store 的组装。该特性的核心契约如下以工厂函数作为输入参数工厂在**注入上下文injection context**中执行因此可以inject服务或依赖工厂的输入参数是当前 store 中已定义的状态信号、属性props与方法的合集工厂返回一个字典dictionary字典中的每个条目就是一个 linked state slice其值可以是计算函数也可以是WritableSignal实例例如linkedSignal或任何手写的可写信号这些切片被正式并入 store 的状态与普通 state slice 待遇一致每个切片都会生成对应的DeepSignal且可以通过patchState更新。从源码看withLinkedState的签名与withState共享同一个SignalStoreFeature组合模型定义见 signal-store-models.ts其输出结果被并入state// with-linked-state.ts核心类型逻辑摘自源码 type LinkedStateResult LinkedStateInput extends Record string | symbol, WritableSignalunknown | (() unknown) , { [K in keyof LinkedStateInput]: LinkedStateInput[K] extends WritableSignalinfer V ? V : LinkedStateInput[K] extends () infer V ? V : never; };即无论传入的是计算函数还是WritableSignal最终都会被推断为对应的状态值类型并作为新的 state slice 暴露给调用方。隐式链接用计算函数声明自动同步的状态切片当 linked state slice 以计算函数的形式提供时SignalStore 会把它自动包装进 Angular 的linkedSignal()中。linkedSignal是“可写”的信号它既像computed一样会随依赖变化而自动重算又允许被手动写入新值。因此这种关联切片会在依赖信号变化时自动更新在通过patchState写入新值后保持该值直到下一次依赖变化再次触发重算。原文档给出了一个完整示例——选项列表 store// options-store.ts import { patchState, signalStore, withLinkedState, withMethods, withState, } from ngrx/signals; export const OptionsStore signalStore( withState({ options: [1, 2, 3] }), withLinkedState(({ options }) ({ // Defining a linked state slice. selectedOption: () options()[0] ?? undefined, })), withMethods((store) ({ setOptions(options: number[]): void { patchState(store, { options }); }, setSelectedOption(selectedOption: number): void { // Updating a linked state slice. patchState(store, { selectedOption }); }, })) );对应组件中使用方式// option-list.ts Component({ // ... other metadata providers: [OptionsStore], }) export class OptionList { readonly store inject(OptionsStore); constructor() { console.log(this.store.selectedOption()); // logs: 1 this.store.setSelectedOption(2); console.log(this.store.selectedOption()); // logs: 2 this.store.setOptions([4, 5, 6]); console.log(this.store.selectedOption()); // logs: 4 } }行为拆解操作结果原因初始读取selectedOption()1计算函数() options()[0]首次求值setSelectedOption(2)2patchState直接写入 linked slice覆盖计算结果setOptions([4, 5, 6])4依赖options变化触发linkedSignal自动重算可以看到linked state slice 兼具“派生”与“可写”双重语义手动写入的值会一直保留直到其依赖信号再次变化。显式链接直接挂载 WritableSignal建立双向同步除了计算函数withLinkedState还接受显式的WritableSignal实例作为 linked state slice。这包括两类使用linkedSignal()并传入source与computation选项创建的信号任意其它WritableSignal实例例如signal()创建的普通可写信号。在这两种情况下SignalStore 与原始信号保持完全同步——任意一侧更新另一侧立即反映同一值。原文档的示例展示了如何用带source/computation的linkedSignal实现“按 id 保选中的选项”这一常见业务逻辑// options-store.ts import { linkedSignal } from angular/core; import { signalStore, withLinkedState, withState, } from ngrx/signals; export const OptionsStore signalStore( withState({ options: [] as Option[] }), withLinkedState(({ options }) ({ selectedOption: linkedSignalOption[], Option({ source: options, computation: (newOptions, previous) { const option newOptions.find( (o) o.id previous?.value.id ); return option ?? newOptions[0]; }, }), })) );这里的关键差异在于linkedSignal的computation回调接收(newOptions, previous)两个参数。当options更新后SignalStore 尝试保留之前选中的选项按previous?.value.id匹配如果旧选中项已不存在则回退到newOptions[0]。这是单纯的计算函数无法优雅实现的行为因为普通计算函数没有对“上一次值”的访问能力。底层实现剖析从源码看状态如何被挂载工厂在注入上下文执行withLinkedState的工厂会在 feature 应用时立即调用并传入当前 store 的状态信号与 props// with-linked-state.ts L86-L94 return (store) { const linkedState linkedStateFactory({ ...store.stateSignals, ...store.props, }); const stateKeys Reflect.ownKeys(linkedState); if (typeof ngDevMode ! undefined ngDevMode) { assertUniqueStoreMembers(store, stateKeys); }这与withMethods见 with-methods.ts、withComputed见 with-computed.ts的机制一致——工厂均在注入上下文中执行所以可以在工厂内使用inject()获取服务例如withLinkedState(({ options }) { const configService inject(ConfigService); // ✅ 允许 return { selectedOption: () configService.pick(options()) }; })计算函数被自动包装为 linkedSignal每个 linked state slice 都会写入 store 的状态源state source。关键在于类型判别与包装逻辑// with-linked-state.ts L98-L104 for (const key of stateKeys) { const signalOrComputationFn linkedState[key]; stateSource[key] isWritableSignal(signalOrComputationFn) ? signalOrComputationFn : linkedSignal(signalOrComputationFn); stateSignals[key] toDeepSignal(stateSource[key]); }如果切片值是WritableSignal通过isWritableSignal判别见 state-source.ts直接原样挂载——这正是显式链接能够与外部原始信号保持同步的原因store 内部持有的就是同一个信号对象否则即计算函数调用 Angular 的linkedSignal(computationFn)自动包装从而获得自动重算 可手动写入的双重能力。isWritableSignal的判定逻辑要求信号同时具备set与update方法// state-source.ts export function isWritableSignal(value: unknown): value is WritableSignalunknown { return ( isSignal(value) set in value update in value typeof value.set function typeof value.update function ); }DeepSignal 与嵌套访问挂载后每个 linked state slice 都通过toDeepSignal见 deep-signal.ts转换为DeepSignal因此与withState创建的状态切片完全对等嵌套属性会按需惰性生成子信号可以直接store.selectedOption()读取整体也可以对对象类型的切片访问嵌套信号。这可以从测试中得到印证modules/signals/spec/with-linked-state.spec.ts中的 “keeps DeepSignal updated” 用例直接读取userStore.stateSignals.user.name并断言其随patchState和外部信号写入同步更新。更新路径patchState 与双向同步linked state slice 的可写性来自两条路径store 内部写入patchState(store, { selectedOption })会定位到状态源中对应的WritableSignal并调用.set()patchState的实现见 state-source.ts L79-L115外部写入仅显式链接由于挂载的是原始信号对象外部对signal.set(...)/signal.update(...)的调用会直接反映到 store 中。对于隐式链接计算函数linkedSignal同样允许手动set因此patchState写入会暂时“冻结”自动计算的结果直到依赖变化再次触发重算——这正是文档示例中setSelectedOption(2)后值保持为2的原因。命名冲突校验与withState一致withLinkedState在开发模式下会对新增切片执行assertUniqueStoreMembers校验防止覆盖已存在的 store 成员。测试 with-linked-state.spec.ts 专门验证了这一点const linkedStateFeature signalStoreFeature( withState({ value: 1 }), withLinkedState(() ({ value: () 1 })) ); // 触发警告 // ngrx/signals: SignalStore members cannot be overridden.与 withState、withComputed 的分工三者都向 store 添加信号但语义不同选择依据如下特性值来源可写性适用场景withState初始状态直接定义通过patchState可写独立、基础的状态切片withComputed依赖其它信号的派生计算computed只读不可写纯派生数据如booksCount、sortedBookswithLinkedState依赖其它信号的派生计算linkedSignal可写既能自动重算也能被patchState覆盖派生但需可覆盖的状态如“默认选中项”、表单选择器核心差异一句话需要“可覆盖的派生值”时用withLinkedState只需要“只读派生值”时用withComputed。withLinkedState把linkedSignal的“派生 可写”双重语义完整地带入了 SignalStore 状态模型。链式依赖linked state 可以依赖另一个 linked state由于 linked state slice 在挂载后与其他状态信号地位相同withLinkedState可以被多次使用并且后一个 feature 的工厂可以访问前一个 feature 生成的切片从而构造级联关联状态。源码的 JSDoc 与测试均覆盖了该模式测试 with-linked-state.spec.ts 中的用例展示了完整链路signalStoreFeature( withState({ id: 1 }), withLinkedState(({ id }) ({ level1: () id() * 2, })), withLinkedState(({ level1 }) ({ level2: () level1() * 10, })) ) // 初始状态{ id: 1, level1: 2, level2: 20 } patchState(store, { id: 2 }); // 状态变为{ id: 2, level1: 4, level2: 40 } patchState(store, { level1: 5 }); // 状态变为{ id: 2, level1: 5, level2: 50 } —— level2 也跟随 level1 重算 patchState(store, { level2: 100 }); // 状态变为{ id: 2, level1: 5, level2: 100 } —— level2 被手动覆盖注意最后的两个细节patchState覆盖level1后依赖它的level2自动重算为50patchState覆盖level2后再次修改id为3level1重算为6被覆盖的level2也重新跟随为60。这说明“手动写入”只会在下一次依赖变化前生效linkedSignal的依赖驱动逻辑在整个链路上始终有效。实战建议与注意事项何时使用withLinkedState需要一个“默认由其它状态推导、但允许用户覆盖”的值。典型场景列表加载后默认选中第一项、根据筛选条件推导默认参数、保留用户上次选择如按 id 匹配需要与 store 外部的WritableSignal共享同一状态保证双向同步需要在 store 内部构造级联的派生状态且每一层都允许被显式覆盖。需要注意的边界状态保护与withState创建的状态一致linked state slice 默认受保护只能通过patchState或原始WritableSignal更新后者仅当你在显式链接中挂载了外部信号时。若确实需要放开外部写入可参考 SignalStore 指南见 SignalStore 总览中的protectedState: false配置依赖信号withLinkedState工厂只接收“已定义”的状态信号、props 与方法。若想依赖尚未定义的成员需要调整 feature 的排列顺序类型推断计算函数返回undefined等联合类型时切片类型会被精确推断如number | undefined这与linkedSignal的类型行为保持一致在组件中注入withLinkedState创建的 store 与其它 SignalStore 一样默认需要在组件、路由或根级别providers中注册后通过inject()获取。小结withLinkedState把 Angular 的linkedSignal能力无缝融入 SignalStore 的状态模型计算函数自动包装、WritableSignal原样挂载、每个切片生成DeepSignal且可用patchState更新。它填补了withState纯状态与withComputed纯派生、只读之间的空白让你能以声明式方式管理“派生但可覆盖”的关联状态。其核心实现集中在 with-linked-state.ts完整的双向同步、级联依赖与命名冲突校验行为均有对应的测试用例with-linked-state.spec.ts可参考验证。赞分享前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载相关推荐Unity MCP Server Docker 部署全指南从本地 Quick Start 到 API Key 鉴权的远程托管模式Unity MCP Server Docker 部署全指南从本地 Quick Start 到 API Key 鉴权的远程托管模式 Unity MCPMode前端状态管理NgRx测试替身使用Jest模拟状态管理依赖NgRx测试替身使用Jest模拟状态管理依赖 你是否在Angular应用测试中遇到过状态管理依赖复杂、测试用例执行缓慢的问题本文将介绍如何使用Jest创建测前端状态管理如何用Next.js Commerce实现模块化状态管理状态切片与依赖注入最佳实践如何用Next.js Commerce实现模块化状态管理状态切片与依赖注入最佳实践 Next.js Commerce是一款基于Next.js构建的现代电商解决电商前端后端上一篇knowledge-work-plugins 之 crm-maintenance用 Cowork Skill 实现 HubSpot CRM 免人工维护的自动化实践指南下一篇gpui-kit Dialog 组件完全指南从基础弹窗到声明式 API 的现代对话框开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
localStorage 在 Chrome 里为何不持久?TaoToken 配置排查与验证 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:19:10
【频道】防入侵!OpenClaw 本地部署对接 QQ:从部署到安全权限锁死全流程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:22
VScode 前端开发配置 TaoToken:settings.json 骨架与验证动作 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:22
OpenClaw 插件系统实战:用 Manifest 扩展你的 AI Agent 边界 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:59:22
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46