游乐游手机版
首页/前端开发/文章详情

Angular与Component Store实战示例

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

正文

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

将逻辑部分分离到 service

思路其实很清晰。用 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 做全局状态管理也完全可行。

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;

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

来源:https://www.jb51.net/article/275683.htm
上一篇Angular实践:将Input与Lifecycle转换为流示例详解 下一篇Angular6升级至Angular8报错问题解决汇总
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
HTML双英雄图精准居中与并排对齐实战指南
前端开发 · 2026-07-04

HTML双英雄图精准居中与并排对齐实战指南

本文详解如何使用CSS Flexbox将两个英雄图在页面中水平居中、等高对齐,并保持50px间距,解决justify-content align-items单独作用于子元素无效的问题。 想让两个视觉冲击力十足的英雄图在首页并排居中,是提升首屏吸引力的经典设计。但很多开发者都踩过同一个坑:直接在 `

Flexbox实现div水平垂直居中的方法
前端开发 · 2026-07-04

Flexbox实现div水平垂直居中的方法

使用 Flexbox 实现 div 的水平垂直居中,推荐在父容器上设置 display: flex,并配合 justify-content: center(控制主轴居中)与 align-items: center(控制交叉轴居中),同时确保父容器拥有明确高度,例如 min-height: 100vh

React循环中正确管理多个独立Modal实例的方法
前端开发 · 2026-07-04

React循环中正确管理多个独立Modal实例的方法

在 React 开发中,我们常常会遇到这样的场景:需要在一个列表循环里渲染多个弹窗(Modal)。如果处理不当,点击任何一个按钮,都会导致所有的弹窗同时打开或关闭,这显然不是我们想要的效果。问题的根源在于状态管理:当多个 Modal 实例共享同一份控制其显示隐藏的状态时,它们的行为就被捆绑在了一起。

鼠标滚动切换图片与7秒无操作自动轮播完整教程
前端开发 · 2026-07-04

鼠标滚动切换图片与7秒无操作自动轮播完整教程

本文介绍如何结合鼠标滚轮交互与定时器机制,实现图片在用户滚动时手动切换、7秒无操作后自动轮播的双重功能,并提供可复用、多实例支持的现代化 JavaScript 解决方案。 在网页开发中,图片轮播组件虽然常见,但许多实现方案在用户体验上仍存遗憾。例如,完全依赖用户滚动切换的轮播,当用户停止操作专注查看

输入新城市自动清除旧天气数据实现方法
前端开发 · 2026-07-04

输入新城市自动清除旧天气数据实现方法

本文详解如何借助 JavaScript 在用户切换查询城市时,自动清空先前展示的天气信息,避免新旧数据混杂叠加,从而优化单页应用的交互体验。 在基于 OpenWeather API 打造天气查询工具时,很多开发者都会遇到一个颇为棘手的小问题:用户查完一个城市后,紧接着输入另一个城市名称,页面上新旧天