You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何以可组合、类型安全方式使用NgRx,避免循环依赖并适配ESLint插件?

问题描述

在Angular项目中使用NgRx时,我希望避免任何NgRx选择器或Angular组件依赖全局的AppState类型(即NgRx Store<T>的泛型参数T),因为这会导致组件与Angular的app.module.ts之间出现源代码循环依赖。

我难以在不牺牲TypeScript类型检查或ESLint检查的前提下实现这一点。当前我使用NgRx 15(搭配Angular 15)以及ESLint的@ngrx/all-requiring-type-checking插件。

不尽人意的临时方案

我有一个存在缺陷的临时解决方案,源自NgRx官方教程,包含以下三段代码:

  • my-counter.ngrx.ts:定义组件状态、NgRx动作、reducer和选择器
import { createAction, createReducer, createSelector, on } from '@ngrx/store';

// 组件状态
export interface MyCounterState { count: number }

// 动作
export const increment = createAction('[Counter Component] Increment');
export const decrement = createAction('[Counter Component] Decrement');

// 归约器
export const counterReducer = createReducer(
  <MyCounterState>{ count: 0},
  on(increment, (state: MyCounterState): MyCounterState => <MyCounterState>{...state, count: state.count + 1}),
  on(decrement, (state: MyCounterState): MyCounterState => <MyCounterState>{...state, count: state.count - 1}),
);

// 选择器
const selectMyCounterState = (state: {myCounterState: MyCounterState}) => state.myCounterState;
const projectCount = (state: MyCounterState): number => state.count;

export const selectCount = createSelector(selectMyCounterState, projectCount);
  • app.module.ts:为Angular应用配置NgRx
@NgModule({
  imports: [
    StoreModule.forRoot({ myCounterState: counterReducer }, {}),
    ...
  ],
  ...
})
export class AppModule { }
  • my-counter.component.ts:使用NgRx Store
@Component(...)
export class MyCounterComponent {

  count$: Observable<number>;

  constructor(private store: Store<{ myCounterState: MyCounterState }>) {
    this.count$ = store.select(selectCount);
  }

  readonly increment = () => this.store.dispatch(increment());
  readonly decrement = () => this.store.dispatch(decrement());
}
现存问题

该临时方案虽可进一步优化(比如用接口替代{ myCounterState: MyCounterState },用常量替代{ myCounterState: counterReducer }),但仍存在问题:

  • { myCounterState: MyCounterState }中的标识符myCounterState与{ myCounterState: counterReducer }中的myCounterState必须手动匹配,TypeScript类型检查器无法在编译时保证这一点,重构工具也无法一致地重命名它们。
    • 这似乎是NgRx API的普遍问题,因为StoreModule.forRoot(...)由NgRx Reducers配置,而NgRx Store的类型是从Reducer的类型签名隐式推导而来。
  • ESLint插件@ngrx/all-requiring-type-checking仍会对组件代码中constructor(private store: Store<{ myCounterState: MyCounterState }>)的泛型参数报错,要求使用原始的constructor(private store: Store)。
    • 但Store本身是泛型类型,提供类型参数对TypeScript在编译时检查选择器的实现和使用至关重要。
核心疑问

如上所述,我难以以一种可组合、类型安全且被NgRx ESLint插件接受的方式使用NgRx(可组合指组件和选择器应独立于任何全局AppState类型,即Store<T>的泛型参数)。请问:

  • 是否存在简洁清晰的实现模式?
  • 或者在当前NgRx 15 API下根本无法实现?

内容的提问来源于stack exchange,提问作者Christian Fuchs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 13:30:11