Angular与Component Store实战示例
Angular中的ComponentStore提供轻量级状态管理,通过选择、更新和效果方法处理局部状态,其生命周期与组件一致。实际使用时,可利用ngOnChanges生命周期钩子批量同步组件的输入属性至Store,从而避免反复声明重复字段,显著提升代码的简洁性和可维护性。
正文
聊聊 Angular 的状态管理。有点意思的是,因为有了 service 和 rxjs,状态管理在 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。使用时可以直接调用 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;
这样一来,代码看起来就干净多了。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
如何用拖放在Flask看板应用中更新数据库状态
在Flask看板应用中,通过HTML5拖放API结合前端fetch异步POST请求,将拖拽任务的task_id和target_state发送至后端路由;后端接收JSON后更新数据库并提交事务,实现跨列拖拽时实时更新状态。需注意将事件绑定到容器而非子元素,并在ondragover中阻止默认行为以确保drop触发。
如何用CSS :has()选择器快速实现悬停span显示相邻div
利用CSS的:has()伪类选择器,通过` bar:has(> baz:hover)+ qux`实现悬停内联元素时显示相邻块级容器,解决了传统相邻兄弟选择器无法跨层级匹配的局限,可用于悬停图标显示详情框等交互场景。需注意浏览器兼容性和性能优化,避免过度使用影响渲染效率。
display:block居中段落文本的响应式实现方法
通过`display:block`与`max-width`限制宽度,配合`margin:auto`使段落块水平居中并实现响应式布局;同时用CSS统一控制段落间距,避免脱离文档流,提升可读性与维护性。
JavaScript代码去重:高效避免重复编写的技巧
将重复代码中的变化点提取为参数,利用ES6模板字符串动态生成选择器,可消除重复、保持一致性。注意参数安全性,必要时进行白名单校验;批量化场景可扩展为数组形式;现代框架中更推荐数据驱动或组件化封装替代手动选择器。
使用容器查询单位cqw实现表单输入框相对于祖父容器百分比宽度
利用CSS容器查询单位cqw,表单输入框宽度可直接基于祖父容器计算,绕过中间包装器尺寸干扰,实现简洁响应式布局,无需硬编码或JavaScript,显著降低维护成本,提升嵌套结构下的布局灵活性。
- 热门数据榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2026-07-09 07:01
2026-07-09 07:01
2026-07-09 07:01
2026-07-09 07:00
2026-07-09 07:00
2026-07-09 07:00
2026-07-09 07:00
2026-07-09 07:00
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

