Angular Signals:响应式编程的新思维模型,不仅仅是新API
Angular Signals 不仅仅是另一个功能。它们代表了一种不同的数据流思考方式。如果你来自 RxJS 或标准的 @Input() / @Output() 绑定,你可能会认为 Signals 是可观察值的更简单语法。然而,这就像说小提琴只是更小的大提琴。乐器的形状影响你创作的音乐类型。
在本文中,我不会重复文档或引导你完成另一个计数器示例。相反,我将介绍 Signals 启用的一种新思维方式,以及我在生产中使用它们时遇到的一些实际挑战。
Signals 作为响应式变量,而非流
将 Signals 视为响应式变量,而不是数据流。这是视角的关键变化。在 RxJS 中,我们通常将值推送到流中,组合它们,并通过 subscribe() 响应副作用。Signals 扭转了这一概念。你像变量一样读取它们。Angular 自动跟踪依赖关系并触发反应。
以下是我在代码中解释 Signals 的最佳方式:
|
|
在这个例子中,当 firstName 或 lastName 更改时,fullName 会自动重新计算。你不需要考虑 map、combineLatest 或拆卸逻辑。你只需声明关系。
如果这感觉像 Vue 或 SolidJS,那并非巧合。
陷阱一:隐式依赖可能适得其反
当你在 computed() 或 effect() 内部读取 Signal 时,Angular 会将该读取跟踪为依赖关系。但当你没有意识到这些读取时,这可能会很快出错。
|
|
你可能期望这仅在计数器更改时运行,但如果你意外地在同一函数内读取另一个 Signal(例如日志记录标志),它也会成为依赖项。突然之间,切换调试模式标志开始重新计算你的数学逻辑。
提示:保持 computed 和 effect 逻辑狭窄且确定。否则,你将无法调试幽灵更新。
Signals 与 RxJS:Signals 的闪光点与不足
让我们明确一点:Signals 不会取代 RxJS。它们设计为与 RxJS 协同工作。但理解何时使用每个至关重要。
使用场景 | 首选 Signals | 首选 RxJS |
---|---|---|
本地组件状态 | ✓ | X |
派生 UI 数据 | ✓ | X |
事件流(例如用户输入) | X | ✓ |
跨模块共享状态(通过服务 Signals) | ✓ | ✓ |
具有重试的复杂异步流 | X | ✓ |
Signals 擅长建模随时间变化的值。RxJS 擅长建模随时间变化的事件。
陷阱二:计算信号不像你想象的那样缓存
我发现的一个令人惊讶的事情:computed() 不像 React 的 useMemo() 那样记忆化,甚至不像你从 getter 中期望的那样。
每次你从 computed() 信号读取时,如果其输入已更改,逻辑会重新运行。但如果你在模板中多次调用它(例如在 *ngIf 中并在 {{ }} 中再次调用),你可能会多次支付成本。
提示:如果计算昂贵,请考虑将其存储在组件类中的本地 const 中,并在模板中仅引用该 const。或者将其包装在另一个信号中。
重新思考状态形状:Signals 喜欢扁平,而非深层
在使用服务和 RxJS 的经典 Angular 中,通常这样建模状态:
|
|
在 Signals 中,深层嵌套的响应式对象很尴尬。你不能说 user().settings().theme() - 那是在读取上的读取上的读取。相反,你会想要扁平化:
|
|
提示:用其自己的信号建模每个状态片段。你将获得灵活性和更轻松的响应式控制。
实际场景:表单标签自定义
假设你有一个 SearchSidebarComponent,并且你想从父级自定义其标签。以下是天真的方式:
|
|
如果你尝试派生计算值会发生什么?
|
|
现在,假设在父级中你写:
|
|
这有效,但你在信号内调用信号。如果你将模板更改为 <search-sidebar [labels]="labelsFinal" />
,它会因类型不匹配而失败。
提示:Angular 的输入系统尚未完全信号原生。在此之前,在传递输入之前扁平化值。
陷阱三:effect() 立即运行 - 可能再次运行
与仅在发出内容时触发的 RxJS subscribe() 不同,effect() 在创建时触发一次,即使信号尚未更改。
|
|
这将立即运行一次,即使 userId 尚未更改。将 HTTP 调用或分析跟踪等副作用放在 effect() 内部时要小心。
提示:如果需要,使用 null 检查或提前返回来保护 effect() 逻辑。
最终想法
Angular 中的 Signals 不仅仅是新语法,它们是思维模型的转变。一旦你停止以可观察对象思考,开始以反应的变量思考,你会发现你的组件更小、更快、更易于推理。
但像任何新工具一样,Signals 带有锐利的边缘。了解权衡,学习模式,最重要的是,不要将 Signals 视为花哨的 getter。它们比那强大得多,但前提是你理解模型。