Angular(v21) 中的 Effects 与 InjectionContext
Source: Dev.to
注入器:真相之源
注入器是一个容器,用来保存服务和依赖的实例。在 Angular 中,存在一系列层级的注入器(环境注入器、组件注入器等)。
当你创建一个 effect 时,它需要 锚定 到某个注入器。注入器为 effect 提供它可能需要的服务,更重要的是,它决定了 effect 的生命周期。
注入上下文:“何时”和“何地”
注入上下文是代码执行期间的一个特定状态,此时 inject() 函数是可用的。可以把它看作对象(组件、指令等)正在构造的那段时间。
默认情况下,你在以下位置拥有注入上下文:
- 类的 构造函数。
- 字段初始化器(例如
myService = inject(MyService))。 - Provider 的 工厂函数。
为什么 effect 需要它?
当你在构造函数中调用 effect(() => { … }) 时,Angular 会隐式地寻找当前的注入上下文,以找到并 “注入” DestroyRef。它利用这个引用来准确地知道何时 “终止” effect,从而避免内存泄漏。
inject() 函数:现代获取器
inject() 函数是从当前注入上下文中获取依赖的编程方式。
如果你尝试在普通方法(例如按钮点击)中创建 effect,它会失败,因为该方法没有注入上下文。
// ✅ WORKS: In the constructor / class init
count = signal(0);
myEffect = effect(() => console.log(this.count()));
// ❌ FAILS: Outside injection context
updateData() {
effect(() => { /* … */ }); // Error: inject() must be called from an injection context
}
手动捕获注入器
export class MyComponent {
private injector = inject(Injector); // Capture the injector in the constructor
startManualEffect() {
// Pass the captured injector to the effect
effect(() => {
console.log('Manual effect running!');
}, { injector: this.injector });
}
}
DestroyRef:清理小组
DestroyRef 是对 OnDestroy 生命周期钩子的现代替代。它允许你在代码的任何位置注册清理逻辑,而不仅限于类定义中。
当创建 effect 时,它内部会订阅其上下文的 DestroyRef。当持有该 effect 的组件或服务被销毁时,DestroyRef 会触发,effect 自动停止。
使用 DestroyRef 手动清理
const dr = inject(DestroyRef);
const sub = mySignal.subscribe(...);
dr.onDestroy(() => {
console.log('Cleaning up resources!');
sub.unsubscribe();
});
runInInjectionContext 辅助函数
有时你已经拥有一个 Injector,但不想把它作为选项传递给每个函数。你可以 “强制” 创建一个上下文:
import { runInInjectionContext } from '@angular/core';
runInInjectionContext(this.injector, () => {
// Anything called here now has access to inject()
effect(() => console.log('I have context!'));
});
代码练习场
关键要点
- Effects 与 Injector 紧密相连。
- Injectors 使用 DestroyRef 进行清理。
inject()只能在注入上下文激活时使用。- 如果在构造函数之外创建 effect,必须手动提供 injector。
祝编码愉快!