首页 > 网页制作 >Angular Component Store实战示例

Angular Component Store实战示例

来源:互联网 2026-06-14 08:14:07

Angular中的ComponentStore提供轻量级状态管理,通过选择、更新和效果方法处理局部状态,其生命周期与组件一致。实际使用时,可利用ngOnChanges生命周期钩子批量同步组件的输入属性至Store,从而避免反复声明重复字段,显著提升代码的简洁性和可维护性。

正文

聊聊 Angular 的状态管理。有点意思的是,因为有了 servicerxjs,状态管理在 Angular 世界里其实并非一个必选项。一个简单的 Beha viorSubject 就能搭起最基础的状态管理架子:

将逻辑部分分离到 service

思路其实很清晰。用 Rxjs 把 service 里的逻辑拆成两块:状态和方法。Component 去 subscribe 状态来驱动视图更新,再通过调用方法来修改状态。就这么回事。

长期稳定更新的攒劲资源: >>>点此立即查看<<<

Angular Component Store实战示例

大家知道,Angular 里也能用 NgRx 提供的 store 来做状态管理,本质上跟 Redux 是同一套东西。但说实话,多数时候,Redux 那套方案确实显得有点重,而且强行把数据和视图分离,用起来未必顺手。

除了 ngrx store,ngrx 还提供了一种更轻量的选择——component store。说白了,它跟我们刚才提到的状态管理模式本质上是一回事,只不过多提供了一层方便的接口。具体可以看 NgRx - @ngrx/component-store。

Angular Component Store实战示例

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

component store 的使用方法

Angular Component Store实战示例

可以看到,store 主要提供三个方法:

  • select——用来拆分 state
  • updater——处理无副作用的 state 更新
  • effect——处理有副作用的情况,然后调用 updater 来更新数据

这接口设计,跟 Mobx 或者 Vuex 挺接近的。区别在于,因为有 RxJS 的存在,它的实现异常简洁——几乎就是给 beha viorSubject 套了层壳。

不过有两点值得留意:

updater 和 effect 的方法参数可以同时接受一个普通值或者一个 observable 值。这意味着,当你在操作一个 stream 时,可以直接把 stream 传进去。

举个例子,假设有个方法 updateUserName: (string | Observable) => void;。使用时可以直接调用 updateUserName('zhangsan')

但有时候,在 component 里拿到的是一个 stream,比如 form.valueChanges。这时候不需要手动 subscribe,可以直接这样:

updateUserName(form.valueChanges.pipe(map(form => form.userName)))

另外,updater 和 effect 在接受 stream 作为参数后,会自动完成 subscribe,并且在 store 销毁时自动 unsubscribe,省去了一堆手动清理的逻辑。

在 component 中使用

在 component 里用起来也简单:

  • 把 component 中必要的数据(通常是 input)投喂给 store
  • 在 component 中调用 updater 或 effect 返回的方法来修改 state
  • 在 component 中 subscribe state,驱动视图渲染
@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 中。

不过,实际用了一段时间后发现,这条路并不总是一帆风顺。

  • 同一个字段,需要在 component 和 store state 里声明两次
  • Input 必须转写成 set 模式
比如 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);
}

在 component 中对于 store 的使用

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;

这样一来,代码看起来就干净多了。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。