我想说,在 NGRX 应用程序的代码中管理 subscription 几乎从来都不是最佳实践。 subscription 可能会导致内存泄漏,如果您的代码在超出范围时无法正确清理它(例如,当创建它的组件被破坏时)。
在典型的应用中,应通过以下两种方式之一安全地使用 Observable:
- 组件可以使用
async 管道来渲染 Observable 的输出
-
@ngrx/effects 可用于处理动作产生的副作用
#1 async 管道
组件可以创建一个 Observable 字段并在 onInit 上设置它:
public titleUppercase$: Observable<string>
ngOnInit(): void {
this.titleUppercase$ = this.store.pipe(select(titleSelector)).map(title => title.toUpperCase());
}
<p>{{titleUppercase$ | async}}</p>
在此设置中,Angular 会自动为您管理订阅,因此不存在内存泄漏的风险。
如果管道逻辑很复杂(例如,使用combineLatest 的多个可观察对象),最好将此逻辑提取到服务类以进行可测试性。
#2 @ngrx/effects
Effects 用于管理“副作用”...即异步进程,它执行的操作不是更改 Store 的状态。从服务器获取数据的HttpClient 调用就是一个副作用示例。
在此处查看@ngrx/effects 库的用户指南:
https://ngrx.io/guide/effects
这是一个 effect 的示例,它从服务器加载数据以响应用户的操作:
loadServerData$ = createEffect(() => this.actions$.pipe(
// I dispatch an action with this type in the onInit method of my view component
ofType('[My Data Page] User loaded the page'),
// each time we see this page load action, trigger a service call to get the HTTP data
switchMap(() => this.myApiService.getData$().pipe(
map(data => {
//the response is mapped to an action called myDataLoaded
//myDataLoaded includes the response payload as a property
return myDataLoaded({payload: data});
}),
//this catchError within the switchMap is necessary to prevent the effect from
//halting if there is an error from the HTTP call
catchError(err => {
// TODO: notify users when there's an error
console.error('Error loading the page data', err);
return NEVER;
}),
//using startsWith inside the switchMap, I can trigger a loading action
//the loading action can be used to display a loading indicator on the page
startWith(myDataLoading())
))
));
使用这种方法,您的应用组件可以简单地观察状态以呈现自己并在用户执行某项操作时调度操作。所有其他在后台发生的复杂业务逻辑都可以使用 reducer 函数和效果来处理。这使您的 UI 应用程序非常容易进行单元测试。
如果您的所有效果都有非常相似的需求并且您想使用沙盒模式,您可能会引入一个通用的可注入依赖项或基类,您的所有效果都可以利用这些依赖项或基类。您还可以拥有一个组件可以分派的常用操作库,甚至是一个可注入服务,它为组件提供沙盒功能,并提供用于分派操作和设置选择器 Observables 的方法。