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
相关产品推荐
相关产品推荐

