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

如何让TypeScript识别返回动态类定义的NGXS工厂函数(Angular16)

解决NGXS动态动作工厂的TypeScript类型识别问题

问题场景

在Angular v16 + NGXS v18项目中,为多个网格组件复用动作逻辑,通过工厂函数生成带唯一type的动作类集合。代码运行正常,但TypeScript无法正确识别对象属性中的类类型:

  1. 直接使用AtgActions.UpdatePagingParams作为参数类型时,TS误将AtgActions识别为命名空间,抛出找不到命名空间的错误
  2. 改用ActionDef泛型定义接口后,动作参数被识别为构造参数数组,无法访问实例属性,同时NGXS装饰器失效

解决方案

核心思路是让TypeScript明确识别动作类的构造函数类型和实例类型,通过调整接口定义、工厂返回类型和使用方式,解决类型推断问题。

1. 定义动作实例类型与构造函数接口

先为每个动作定义实例类型,再构造包含构造函数签名的GridActions接口,让TS能关联构造函数和实例类型:

// 定义每个动作的实例类型
interface InitializeAction {
  readonly type: string;
}

interface FetchAction {
  readonly type: string;
}

interface FetchSuccessAction {
  readonly type: string;
  payload: SomeSwaggerDTO;
}

interface FetchFailedAction {
  readonly type: string;
  error: Error;
}

interface UpdatePagingParamsAction {
  readonly type: string;
  pageEvent: PageEvent;
}

// 其他动作实例类型同理定义...

// 定义GridActions为构造函数集合,每个属性对应动作的构造函数签名
export interface GridActions {
  Initialize: new () => InitializeAction;
  Fetch: new () => FetchAction;
  FetchSuccess: new (payload: SomeSwaggerDTO) => FetchSuccessAction;
  FetchFailed: new (error: Error) => FetchFailedAction;
  UpdatePagingParams: new (pageEvent: PageEvent) => UpdatePagingParamsAction;
  // 其他动作的构造函数签名...
}

2. 重构GridActionFactory实现

确保工厂返回的对象严格符合GridActions接口,同时为每个动作类实现对应的实例类型,让TS能正确推断实例属性:

export class GridActionFactory {
  private cache: { [key: string]: GridActions } = {};
  private static instance: GridActionFactory;

  static getActions(gridName: string): GridActions {
    if (!this.instance) {
      this.instance = new GridActionFactory();
    }

    if (this.instance.cache[gridName]) {
      return this.instance.cache[gridName];
    }

    const prefix = `[Example]`;
    const newActionCollection: GridActions = {
      Initialize: class implements InitializeAction {
        static readonly type = `${prefix} Initialize ${gridName} Grid State`;
        readonly type = (this.constructor as typeof Initialize).type;
      },
      Fetch: class implements FetchAction {
        static readonly type = `${prefix} Fetch ${gridName} Grid`;
        readonly type = (this.constructor as typeof Fetch).type;
      },
      FetchSuccess: class implements FetchSuccessAction {
        static readonly type = `${prefix} Fetch ${gridName} Success`;
        readonly type = (this.constructor as typeof FetchSuccess).type;
        constructor(public payload: SomeSwaggerDTO) {}
      },
      FetchFailed: class implements FetchFailedAction {
        static readonly type = `${prefix} Fetch ${gridName} Failed`;
        readonly type = (this.constructor as typeof FetchFailed).type;
        constructor(public error: Error) {}
      },
      UpdatePagingParams: class implements UpdatePagingParamsAction {
        static readonly type = `${prefix} Update ${gridName} Paging Params`;
        readonly type = (this.constructor as typeof UpdatePagingParams).type;
        constructor(public pageEvent: PageEvent) {}
      },
      // 其他动作类实现同理...
    };

    this.instance.cache[gridName] = newActionCollection;
    return newActionCollection;
  }
}

3. 正确声明动作集合的类型

使用as const保留字面量类型,同时定义类型别名方便后续使用:

// 生成具体的动作集合
export const ActionableGridActions = GridActionFactory.getActions('actionable-grid');
export const PendingGridActions = GridActionFactory.getActions('pending-grid');
export const HistoricalGridActions = GridActionFactory.getActions('historical-grid');

// 导出动作集合,用as const让TS保留完整类型信息
export const ExampleActions = {
  ActionableGrid: ActionableGridActions,
  PendingGrid: PendingGridActions,
  HistoricalGrid: HistoricalGridActions
} as const;

// 定义类型别名,简化后续类型引用
export type ActionableGridActions = typeof ActionableGridActions;
export type HistoricalGridActions = typeof HistoricalGridActions;

4. 在State中正确使用类型

通过InstanceType工具类型从构造函数类型中提取实例类型,让TS正确识别动作参数的属性:

import { ExampleActions, ActionableGridActions } from './example.actions';

const AtgActions = ExampleActions.ActionableGrid;

export const EXAMPLE_STATE_TOKEN = new StateToken<ExampleStateModel>('example');

@State({
  name: EXAMPLE_STATE_TOKEN,
  defaults: exampleStateModelDefaults,
})
@Injectable()
export class ExampleState {
  @Action(AtgActions.UpdatePagingParams)
  updateActionableGridPagingParams(
    ctx: StateContext<ExampleStateModel>,
    action: InstanceType<ActionableGridActions['UpdatePagingParams']>
  ) {
    ctx.setState(
      produce(draft => {
        draft.actionableGridComponent.pagingParams.pageIndex = action.pageEvent.pageIndex;
        draft.actionableGridComponent.pagingParams.previousPageIndex = action.pageEvent.previousPageIndex;
        draft.actionableGridComponent.pagingParams.pageSize = action.pageEvent.pageSize;
      })
    );
    ctx.dispatch(new AtgActions.Fetch());
  }
}

问题原因说明

  • 最初的错误是因为TypeScript在类型位置使用对象属性的类时,默认不会将其识别为类的实例类型,需要明确通过InstanceType提取
  • 使用ActionDef时,泛型的第一个参数是构造函数的参数数组,直接引用GridActions["UpdateFilter"]得到的是构造函数类型而非实例类型,导致无法访问实例属性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 05:08:09