Angular中的ComponentStore提供轻量级状态管理,通过选择、更新和效果方法处理局部状态,其生命周期与组件一致。实际使用时,可利用ngOnChanges生命周期钩子批量同步组件的输入属性至Store,从而避免反复声明重复字段,显著提升代码的简洁性和可维护性。
聊聊 Angular 的状态管理。有点意思的是,因为有了 service 和 rxjs,状态管理在 Angular 世界里其实并非一个必选项。一个简单的 Beha viorSubject 就能搭起最基础的状态管理架子:
思路其实很清晰。用 Rxjs 把 service 里的逻辑拆成两块:状态和方法。Component 去 subscribe 状态来驱动视图更新,再通过调用方法来修改状态。就这么回事。
长期稳定更新的攒劲资源: >>>点此立即查看<<<

大家知道,Angular 里也能用 NgRx 提供的 store 来做状态管理,本质上跟 Redux 是同一套东西。但说实话,多数时候,Redux 那套方案确实显得有点重,而且强行把数据和视图分离,用起来未必顺手。
除了 ngrx store,ngrx 还提供了一种更轻量的选择——component store。说白了,它跟我们刚才提到的状态管理模式本质上是一回事,只不过多提供了一层方便的接口。具体可以看 NgRx - @ngrx/component-store。

怎么理解这俩的定位差异?参考一下 React 里 hooks 和 redux 的区别就清楚了。Ngrx store 适合处理全局状态,而 Component Store 更适合处理组件内部局部的状态管理——它的生命周期跟组件本身一致。当然,实际用下来,拿 component store 做全局状态管理也完全可行。

可以看到,store 主要提供三个方法:
这接口设计,跟 Mobx 或者 Vuex 挺接近的。区别在于,因为有 RxJS 的存在,它的实现异常简洁——几乎就是给 beha viorSubject 套了层壳。
不过有两点值得留意:
updater 和 effect 的方法参数可以同时接受一个普通值或者一个 observable 值。这意味着,当你在操作一个 stream 时,可以直接把 stream 传进去。
举个例子,假设有个方法 updateUserName: (string | Observable。使用时可以直接调用 updateUserName('zhangsan')。
但有时候,在 component 里拿到的是一个 stream,比如 form.valueChanges。这时候不需要手动 subscribe,可以直接这样:
updateUserName(form.valueChanges.pipe(map(form => form.userName)))
另外,updater 和 effect 在接受 stream 作为参数后,会自动完成 subscribe,并且在 store 销毁时自动 unsubscribe,省去了一堆手动清理的逻辑。
在 component 里用起来也简单:
@Component({
template: `...`,
// MoviesStore is provided higher up the component tree
})
export class MovieComponent {
movie$: Observable;
@Input()
set movieId(value: string) {
// calls effect with value. Notice it's a single string value.
this.moviesStore.getMovie(value);
this.movie$ = this.moviesStore.selectMovie(value);
}
constructor(private readonly moviesStore: MoviesStore) {}
}
当然,也可以做些优化。比如把逻辑尽可能放到 store 里,component 只管简单调用;数据间的联动关系放在 store 的 constructor 里,component 只做调用即可。
@Component({
template: `...`,
// MoviesStore is provided higher up the component tree
})
export class MovieComponent {
movie$: Observable;
@Input()
set movieId(value: string) {
this.mobiesStore.patchState({movieId: value});
}
constructor(private readonly moviesStore: MoviesStore) {}
}
@Injectable() export class MoviesStore extends ComponentStore{ constructor(private readonly moviesService: MoviesService) { super({movieId: string; movies: []}); this.geMovie(this.movieId$); } movieId$ = this.select(state => state.movieId); movie$ = this.moviesStore.selectMovie(this.movieId$); readonly getMovie = this.effect((movieId$: Observable ) => { //.... }); readonly addMovie = this.updater((state, movie: Movie) => ({ // ... })); selectMovie(movieId: string) { // ... } }
因为 component store 是针对单个组件的,通常它的使用场景是逻辑比较复杂的组件。一个 component 基于 input 的变化,完全可以转化为对 store 的监听。这样一来,基本上可以把 component 的大部分 input 同步到 store 中。
不过,实际用了一段时间后发现,这条路并不总是一帆风顺。
比如 userName 这个字段
原来:
@Input userName: string;
与 store 同步:
@Input
set userName(val: string) {
this.store.patchState({userName: val});
}
如果想在 component 中直接调用 userName 就更麻烦了。
private _userName: string;
@Input
set userName(val: string) {
this._userName = val;
this.store.patchState({userName: val});
}
get userName() {
return this._userName;
}
字段一多,这简直就是灾难。
最近尝试了一种不同于官网推荐的方法。我们知道,除了 set,还有一种更常规的方法来获取 input changes——那就是 ngChanges。
export function mapPropChangesToStore(this: ComponentStore , mappedKeys: readonly string[], changes: SimpleChanges) { const state = mappedKeys.reduce((prev: Partial , propKey) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const propValue = changes.[propKey]; if (!propValue) { return prev; } return ({ ...prev, [propKey]: propValue.currentValue, }); }, {}); if (isEmpty(state)) { return; } this.patchState(state); }
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { mapPropChangesToStore } from '@dashes/ngx-shared';
import { componentInputs, CsDemoStore } from './cs-demo.store';
@Component({
selector: 'app-cs-demo',
templateUrl: './cs-demo.component.html',
styleUrls: ['./cs-demo.component.css']
})
export class CsDemoComponent implements OnChanges {
@Input() p1!: string;
@Input() p2!: string;
@Input() p3!: string;
constructor(public store: CsDemoStore) { }
ngOnChanges(changes: SimpleChanges): void {
mapPropChangesToStore.bind(this.store)(componentInputs, changes);
}
}
export const componentInputs = ['p1', 'p2'] as const;
export type State = Pick;
这样一来,代码看起来就干净多了。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述