如何以可组合、类型安全方式使用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的类型签名隐式推导而来。
- 这似乎是NgRx API的普遍问题,因为
- 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
相关产品推荐
相关产品推荐

