反应式编程已成为现代前端开发的核心范式,框架如 Angular 也大量依赖于 Observables、EventEmitters 和状态管理。然而,随着 Angular 引入了 Signals,它提供了一种更简单、更直观的方式来管理本地状态。
在本文中,我们将从基础知识到高级用例详细介绍 Angular 中的 Signals,解释它们的工作原理,与可观察对象有何不同,以及如何使用它们构建高性能的 Angular 应用程序。
什么是 Angular 信号呢?
Angular 信号是反应式值,当它们的依赖项发生变化时会自动更新自己。你可以把它们想象成反应式变量:它们轻量、同步且易于使用,非常适合管理组件的局部状态。与通常用来处理异步数据流的可观察对象不同,信号是同步的,并且与 Angular 的变更检测机制非常兼容。
在 Angular 中,一个信号量表示一个会随时间变化的值,其他信号量可以依赖于它。这在你的应用程序中形成了自然的反应式状态管理流程,其中更新会自动传播开。
例子:
import { signal } from '@angular/core';
// 定义一个信号量
const count = signal(0);
// 更新信号量的值
count.set(1);
// 读取信号量的值
console.log(count()); // 打印: 1
点击全屏模式。退出全屏。
信号和可观察对象的区别:它们有何不同
尽管“observables”和“signals”都是反应式编程结构,但它们在用途和行为上存在显著差异。
特性 | 信号 | 可观察对象 |
---|---|---|
推送 vs 拉取(推 vs 拉) | 拉取式(按需获取) | 推送式(值被发出) |
同步 | 同步且即时 | 可以是异步 |
订阅 | 不需要 | 需要 |
使用场景 | 本地状态 | 流和异步操作 |
虽然可观察对象更适合适用于事件流(如 HTTP 请求、WebSocket 消息),信号在处理本地、同步状态时更出色,这种状态在 Angular 组件内部需要被访问和更新。
在 Angular 中配置信号
要开始在 Angular 中使用信号,请确保你正在使用的是 Angular 16 或更高版本,因为这是信号功能已经完全集成到 Angular 核心中的版本。以下是一个基本的 Angular 组件中使用信号的示例:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<button (click)="增加()">点击增加</button>
<p>计数: {{ count() }}</p>
`
})
export class CounterComponent {
count = signal(0);
增加() {
this.count.set(this.count() + 1);
}
}
全屏 退出全屏
高级概念:衍生信号与计算信号
Angular 让你可以创建 衍生信号(这些信号依赖于其他信号),当其依赖发生变化时会自动更新。这使得处理复杂的状态依赖变得更容易。
派生信号
派生信号非常适合用来处理依赖其他信号计算得出的值。
比如,这里有一个简单的创建派生信号的例子:
const baseCount = signal(10);
const doubleCount = computed(() => baseCount() * 2);
console.log(doubleCount()); // 输出结果为: 20
点击全屏 点击退出全屏
在这个例子中,doubleCount
总是返回 baseCount
值的两倍。如果 baseCount
有变化,doubleCount
会自动跟着变。
计算生成的信号:
你也可以创建计算生成的信号,它们类似于派生信号,但当你需要进行更复杂的计算时,可以使用它们。
/**
* 下面是一个简单的计算示例。
* 这里我们使用了 `signal` 和 `computed` 来计算两个信号的和。
*/
const a = signal(5);
const b = signal(3);
const sum = computed(() => a() + b());
console.log(sum()); // 控制台输出:8
全屏显示 退出全屏
每当 a
或 b
变化时,sum
值会自动更新。
Angular 变化检测中的信号整合
信号的一个关键优势是它们与 Angular 的变更检测机制深度整合。信号会自动触发视图更新,即使在使用 OnPush 策略时也不例外。这显著提升了性能,而无需手动调用变更检测。
关于 OnPush 的示例:
@Component({
selector: 'app-signal-example',
template: `<p>{{ 全名() }}</p>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SignalExampleComponent {
firstName = signal('Soumaya');
lastName = signal('Erradi');
全名 = computed(() => `${this.firstName()} ${this.lastName()}`);
}
全屏显示,退出全屏
即使启用了OnPush,当firstName
或lastName
(即名字或姓氏)发生变化时,视图依然会更新,这是由于信号的响应特性。
复杂应用中的信号处理技术
在更复杂的场景下,例如管理整个应用程序的状态,信号可以简化状态管理,通过明确声明各个部分之间的依赖关系。例如,在购物车应用中,你可以利用信号来管理购物车中的商品,并根据添加的商品自动计算总价。
例子:带有信号指示的购物车
@Injectable({ providedIn: 'root' })
export class 购物车服务 {
private items = 信号<购物车项[]>([]);
添加购物车项(item: 购物车项) {
this.items.set([...this.items(), item]);
}
获取总价 = 计算(() =>
this.items().reduce((total, item) => total + item.price, 0)
);
}
全屏模式,退出全屏
每当购物车中的商品被添加或移除时,getTotalPrice
信号会自动更新。
信号的最佳做法
在使用Angular应用中的信号时,以下是一些最佳实践,以实现最佳性能和可维护性:
- 使用信号来管理本地和同步状态:信号非常适合管理组件状态或组件内的任何同步数据依赖。
- 保持衍生信号简单:虽然衍生信号很有用,但尽量减少依赖关系,避免过于复杂的信号链,这会使调试变得复杂。
- 利用自动更新功能:不要通过手动跟踪状态更新来使基于信号的组件过于繁琐。让 Angular 自动处理状态更新。
- 结合信号和可观察对象:在某些情况下,你可能仍然需要使用可观察对象来管理异步数据。在这种情况下,使用信号来管理同步状态,使用可观察对象来处理异步数据流。
总结
Angular Signals 引入了一种新的方法来管理 Angular 应用程序中的反应式状态。它们提供了一种轻量级、同步且声明式的方式来管理状态,使您的应用程序更容易理解和推理,并且更易于维护。随着您构建更复杂的应用程序,信号可以减少样板代码的需求,消除手动订阅的需求,并简化您的变更检测。
有了这些基础知识,你就可以开始利用信号构建更高性能、更灵敏地响应的Angular应用程序了。
编程开心!