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 Signals不像你想象的那样缓存
我发现的一个令人惊讶的事情:computed()不像React的useMemo()那样记忆化,甚至不像你从getter中期望的那样。
每次你从computed() signal读取时,如果其输入已更改,逻辑会重新运行。但如果你在模板中多次调用它(例如在*ngIf中再次在{{ }}中),你可能会多次支付成本。
提示:如果计算成本高昂,请考虑将其存储在组件类的本地const中,并在模板中仅引用该const。或者将其包装在另一个signal中。
重新思考状态形状:Signals喜欢扁平,而非深层
在使用服务和RxJS的经典Angular中,通常这样建模状态:
|
|
在Signals中,深度嵌套的响应式对象很尴尬。你不能说user().settings().theme() - 那是对读取的读取。相反,你会想要扁平化:
|
|
提示:用其自己的signal建模每个状态片段。你将获得灵活性和更轻松的响应式控制。
实际场景:表单标签自定义
假设你有一个SearchSidebarComponent,并且你想从父组件自定义其标签。以下是天真的方式:
|
|
如果你尝试派生一个computed值会发生什么?
|
|
现在,假设在父组件中你写:
|
|
这有效,但你在signal内部调用signal。如果你将模板更改为<search-sidebar [labels]=“labelsFinal” />,它会因类型不匹配而失败。
提示:Angular的输入系统尚未完全signal-native。在此之前,在传递输入之前展平值。
陷阱三:effect()立即运行 - 可能再次运行
与仅在发出内容时触发的RxJS subscribe()不同,effect()在创建时触发一次,即使signal尚未更改。
|
|
即使userId尚未更改,这也会立即运行一次。在effect()内部放置如HTTP调用或分析跟踪等副作用时要小心。
提示:如果需要,使用null检查或提前返回来保护effect()逻辑。
最终想法
Angular中的Signals不仅仅是新语法,它们是思维模型的转变。一旦你停止思考observables并开始思考响应的变量,你会发现你的组件更小、更快、更易于推理。
但像任何新工具一样,Signals带有尖锐的边缘。了解权衡,学习模式,最重要的是,不要将Signals视为花哨的getter。它们比那更强大,但前提是你理解模型。