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

NgRx Effects类型不兼容错误排查求助

问题描述

类型不兼容错误信息如下:

Argument of type 'MonoTypeOperatorFunction<Action<void>>' is not assignable to parameter of type 'OperatorFunction<Action, Action<void>>'.
  Types of parameters 'source' and 'source' are incompatible.
    Type 'Observable<Action>' is not assignable to type 'Observable<Action<void>>'.
      Type 'Action' is not assignable to type 'Action<void>'.
        Property 'payload' is missing in type 'Action' but required in type 'Payload<void>'.ts(2345)
index.ts(9, 14): 'payload' is declared here.

相关代码

Effect代码

import { Injectable } from "@angular/core";
import { Actions, createEffect } from "@ngrx/effects";
import { Observable, of } from "rxjs";
import { catchError, map, switchMap } from 'rxjs/operators';
import { LocationPinsService } from "src/app/services/location-pins.services";
import { isAction } from ".";
import { LocationActions } from "../actions/location.actions";

@Injectable()
export class LocationEffects {

    getAllPins$: Observable<any> = createEffect(() => this._action$.pipe(
        isAction(LocationActions.GetAllLocationPins),
        switchMap(() =>
             this._locationPins.getAllPins().pipe(
                map((response) => LocationActions.GetAllLocationPinsSuccess(response)),
                catchError((response) => of(LocationActions.GetAllLocationPinsFail(response)))
            )
        )
    ));

constructor(private _locationPins: LocationPinsService, private _action$: Actions) {}
}

Actions代码

import { Injectable } from "@angular/core";
import { Store } from "@ngrx/store";
import { ApiResponse } from "src/app/models/api.response.model";
import { locationPins } from "src/app/models/locatinon-pins.model";
import { createActionCreator, createBoundActionCreator, createType } from "..";
import { AppState } from "../state/app.state";

@Injectable({ providedIn: 'root' })
export class LocationActions {
    static GET_ALL_PINS = createType('[Location Actions] Get All Location Pins');
    static GET_ALL_PINS_SUCCESS = createType('[Location Actions] Get All Location Pins Success');
    static GET_ALL_PINS_FAIL = createType('[Location Actions] Get All Location Pins Fail');

    static GetAllLocationPins = createActionCreator<void>(LocationActions.GET_ALL_PINS);
    static GetAllLocationPinsSuccess = createActionCreator<ApiResponse<locationPins[]>>(LocationActions.GET_ALL_PINS_SUCCESS);
    static GetAllLocationPinsFail = createActionCreator<ApiResponse<void>>(LocationActions.GET_ALL_PINS_FAIL);

    constructor(private store: Store<AppState>) {}
 
    getAllLocationPins = createBoundActionCreator(LocationActions.GetAllLocationPins, this.store.dispatch.bind(this.store));

}

Effects Index代码

import { Action, TypedActionCreator } from '..';
import { MonoTypeOperatorFunction } from 'rxjs';
import { filter } from 'rxjs/operators';

export function isAction<T extends Action<F>, F>(...actionCreators: TypedActionCreator<F>[]): MonoTypeOperatorFunction<Action<F>> {
    return filter((action: Action<F>): action is T => {
        return actionCreators.some(fn => fn.type === action.type);
    });
}

Store Index代码

/*  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/*              TYPES                           */
/*  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
type Type = {
    readonly type: string;
}

type Payload<T> = {
    readonly payload: T;
}

type Action<T>  = Type & Payload<T>;

type ActionCreator<T> = {
    (payload: T): Action<T>;
};

type TypedActionCreator<T> = Type & ActionCreator<T>;

type BoundActionCreator<T> = (payload: T) => void;

/*  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/*              FACTORY FUNCTIONS               */
/*  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function createType(type: string): Type {
    return { type };
}

function createPayload<T>(payload: T): Payload<T> {
    return { payload };
}

function createAction<T>(type: Type, payload: Payload<T>): Action<T> {
    return { 
        type: type.type,
        payload: payload.payload
    };
}

function createActionCreator<T>(type: Type): TypedActionCreator<T> {
    const actionCreator = (payload: T) => createAction(type, createPayload(payload)); 
    (actionCreator as any).type = type.type;
    return actionCreator as TypedActionCreator<T>;    
}

function createBoundActionCreator<T>(actionCreator: ActionCreator<T>, dispatchFn: (action: Action<T>) => void): BoundActionCreator<T> {
    return (payload: T) => {
        const action = actionCreator(payload);
        dispatchFn(action);   
    };
}

/*  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/*              TYPE GUARDS                     */
/*  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function isType<T>(action: Action<any>, ...expectedAction: Action<T>[]): action is Action<T> {
    return expectedAction.some(e => e.type === action.type)
}

function isAction<T>(action: Action<any>, ...expectedActionCreator: TypedActionCreator<T>[]): action is Action<T> {
    return expectedActionCreator.some(e => e.type === action.type);
}

export function notNull(d: any) {
    return d != null && d != undefined;
}

export {
    Type, Payload, Action,
    TypedActionCreator,
    createType,
    createPayload,
    createAction,
    createActionCreator,
    createBoundActionCreator,
    isType,
    isAction
};
问题分析
  • 核心矛盾:自定义Action<T>类型强制要求包含payload字段,但NgRx的Actions流默认发出无泛型的Action类型,缺少payload定义,导致与isAction操作符期望的Action<void>不兼容。
  • isAction函数泛型定义冗余且不准确,无法正确处理输入输出类型转换(输入为Observable<Action>,输出应为具体Action类型)。
  • createActionCreator针对void类型的action仍要求传入payload,不符合无参数action的使用场景。
解决方案

1. 修改Store的Action类型,支持无Payload场景

让Payload字段可选,适配无参数action:

// Store Index.ts
type Type = {
    readonly type: string;
}

type Payload<T> = {
    readonly payload: T;
}

// 让Payload可选,支持无参数Action
type Action<T = void> = Type & Partial<Payload<T>>;

2. 修正isAction操作符的泛型逻辑

调整泛型定义,让操作符能从通用Action流过滤出指定类型:

// Effects Index.ts
import { Action, TypedActionCreator } from '..';
import { OperatorFunction } from 'rxjs';
import { filter } from 'rxjs/operators';

export function isAction<T extends Action>(...actionCreators: TypedActionCreator<any>[]): OperatorFunction<Action, T> {
    return filter((action: Action): action is T => {
        return actionCreators.some(fn => fn.type === action.type);
    });
}

3. 在Effects中指定Actions的泛型类型

明确注入的Actions类型为自定义的Action:

// Effect代码
import { Actions, createEffect } from "@ngrx/effects";
// 导入自定义Action类型
import { Action } from "../..";

@Injectable()
export class LocationEffects {

    getAllPins$: Observable<Action> = createEffect(() => this._action$.pipe(
        isAction(LocationActions.GetAllLocationPins),
        switchMap(() =>
             this._locationPins.getAllPins().pipe(
                map((response) => LocationActions.GetAllLocationPinsSuccess(response)),
                catchError((response) => of(LocationActions.GetAllLocationPinsFail(response)))
            )
        )
    ));

constructor(private _locationPins: LocationPinsService, private _action$: Actions<Action>) {}
}

4. 调整createActionCreator,支持无Payload调用

当T为void时,允许action creator无需传入payload:

// Store Index.ts
function createActionCreator<T = void>(type: Type): TypedActionCreator<T> {
    const actionCreator = (payload?: T) => createAction(type, payload ? createPayload(payload) : undefined); 
    (actionCreator as any).type = type.type;
    return actionCreator as TypedActionCreator<T>;    
}

function createAction<T = void>(type: Type, payload?: Payload<T>): Action<T> {
    return { 
        type: type.type,
        ...(payload && { payload: payload.payload })
    };
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 22:20:30