Skip to content

生命周期与资源清理 ​

组件生命周期不是“初始化/销毁”两个钩子。输入变化、视图创建、渲染后和销毁各有适用责任;订阅、AbortController、observer 和 DOM listener 必须由创建者清理。

ts
const destroyRef = inject(DestroyRef);
fromEvent(window, 'resize').pipe(takeUntilDestroyed(destroyRef)).subscribe(update);

实验:路由切换 10 次,检查 listener 和请求计数不会累积。

练习:把手写 unsubscribe 改成与 DestroyRef 对称的清理。

核心机制与实现 ​

DestroyRef 把清理责任绑定到当前注入上下文;takeUntilDestroyed 会在组件销毁时退订 Observable,但不会替你取消不相关的 Promise 或关闭 WebSocket。

ts
const destroyRef = inject(DestroyRef)
const stop = new AbortController()
fromEvent(window, 'resize')
  .pipe(takeUntilDestroyed(destroyRef))
  .subscribe(updateLayout)
fetch('/api/layout', { signal: stop.signal }).catch(ignoreAbort)
destroyRef.onDestroy(() => stop.abort())

路由切换十次后,resize 监听器数量应保持为一个活动实例;请求被取消时只能记录 AbortError,不能把它显示成网络失败。删除 takeUntilDestroyed 或 stop.abort() 各运行一次,测试应分别失败,这样才能证明两条清理边界都被覆盖。

本节专属实验:把输入追到视图 ​

在 Angular 组件中只保留本节主题的一条数据流:输入值、状态变更、模板读取和清理日志分别打印。先运行成功路径,再删除本节涉及的绑定或 provider,记录是模板编译、依赖注入还是运行时错误。

ts
const before = signal('idle')
const after = computed(() => before() === 'idle' ? 'ready' : 'busy')
effect(() => console.log('state', before(), after()))

预期:before 改变时只重新计算依赖它的 computed;组件销毁后 effect 不再产生输出。

可运行实验 ​

  1. 在组件、注入服务和路由切换之间记录实例创建与销毁。
  2. 对模板读取、signal 更新、Observable 订阅和 HTTP 请求分别加日志。
  3. 用 Angular 测试工具验证可访问性、错误状态、取消请求和 SSR/hydration 边界。

实验记录至少包含:环境版本、输入序列、预期结果、实际结果、失败截图或日志,以及修复后再次运行的结果。

练习 ​

  1. 用自己的话画出本篇概念的依赖图,并标出一个可能产生重复执行或竞态的节点。
  2. 写一个最小测试覆盖成功、失败、取消/卸载三条路径;测试应断言可观察结果,而不是内部实现调用次数。
  3. 给出一个选择当前方案而不是替代方案的理由,并写下替代方案何时更合适。

查看本节答案

下一步 ​

完成本节练习后阅读本章答案,再继续本章下一节。

用心记录每一步成长