使用@ngrx/effects遇类型错误,如何在Angular中顺序执行@Ngrx动作?
解决@ngrx/effects的TypeError及顺序执行动作问题
嘿,我来帮你搞定这个@ngrx/effects的问题!你遇到的这个TypeError: 你提供了'undefined',但预期是一个流本质上是因为你的Effect没有返回一个有效的数据流——要么你不小心return了undefined,要么流处理逻辑有漏洞。结合你要顺序执行两个动作的需求,咱们一步步来解决:
先搞懂错误根源
这个错误90%的情况都是以下两种原因:
- Effect的返回值不是合法的流(Observable/Promise/Array/Iterable),比如你在pipe操作里漏掉了return,或者某个操作符返回了undefined
- 你尝试从store获取数据时,选择器返回了初始状态(还没被第一个动作更新),后续流处理逻辑因为数据为空直接中断,导致返回undefined
正确的顺序执行方案
你的需求是:第一个动作更新状态 → 第二个动作使用更新后的状态触发服务器调用,最规范的做法是用「动作监听+状态同步获取」的方式,而不是在同一个Effect里强行串行dispatch。
步骤1:定义动作
先把需要的动作都定义好,明确每个动作的职责:
// src/app/store/data.actions.ts import { createAction, props } from '@ngrx/store'; // 触发数据更新的动作 export const updateData = createAction('[Data] Update Data', props<{ data: any }>()); // 数据更新成功的动作(会触发reducer更新状态) export const updateDataSuccess = createAction('[Data] Update Data Success', props<{ data: any }>()); // 触发服务器调用的动作 export const callServer = createAction('[API] Call Server', props<{ data: any }>()); // 服务器调用成功/失败的动作(可选,用于处理结果) export const callServerSuccess = createAction('[API] Call Server Success', props<{ response: any }>()); export const callServerFailure = createAction('[API] Call Server Failure', props<{ error: string }>());
步骤2:Reducer处理状态更新
确保updateDataSuccess动作能正确更新store状态:
// src/app/store/data.reducer.ts import { createReducer, on } from '@ngrx/store'; import * as DataActions from './data.actions'; export interface DataState { currentData: any; // 存储你需要的业务数据 } export const initialState: DataState = { currentData: null }; export const dataReducer = createReducer( initialState, on(DataActions.updateDataSuccess, (state, { data }) => ({ ...state, currentData: data // 用新数据覆盖旧状态 })) );
步骤3:用Effects编排流程
这里分两个Effect,分别处理「状态更新」和「服务器调用」,通过动作的触发顺序保证串行:
// src/app/store/data.effects.ts import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of } from 'rxjs'; import { map, switchMap, withLatestFrom, catchError } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import * as DataActions from './data.actions'; import { selectCurrentData } from './data.selectors'; // 提前定义好的选择器 import { YourApiService } from '../services/your-api.service'; // 你的API服务 @Injectable() export class DataEffects { // 第一个Effect:处理数据更新,触发状态更新动作 updateData$ = createEffect(() => this.actions$.pipe( ofType(DataActions.updateData), // 如果是同步更新,直接映射到success动作;如果是异步获取数据,这里可以加switchMap调用API map(({ data }) => DataActions.updateDataSuccess({ data })) ) ); // 第二个Effect:监听状态更新完成的动作,获取最新状态后调用服务器 callServerAfterUpdate$ = createEffect(() => this.actions$.pipe( ofType(DataActions.updateDataSuccess), // 监听状态更新成功的动作 withLatestFrom(this.store.select(selectCurrentData)), // 获取更新后的最新状态 switchMap(([_, currentData]) => { // 边界处理:如果数据为空,返回错误动作,避免流中断 if (!currentData) { return of(DataActions.callServerFailure({ error: '无可用数据' })); } // 调用服务器接口 return this.apiService.sendDataToServer(currentData).pipe( map(response => DataActions.callServerSuccess({ response })), // 处理API调用错误 catchError(error => of(DataActions.callServerFailure({ error: error.message }))) ); }) ) ); constructor( private actions$: Actions, private store: Store, private apiService: YourApiService ) {} }
关键细节解释
- 用
withLatestFrom获取最新状态:因为updateDataSuccess动作已经触发reducer完成了状态更新,此时用withLatestFrom能确保拿到的是最新的状态数据,不会出现旧数据或undefined的情况 - 每个Effect都返回有效流:哪怕是错误场景,也要用
of()返回一个包含错误动作的Observable,绝对不能让Effect返回undefined - 动作职责单一:每个动作只做一件事,通过动作的触发顺序来实现流程串行,这是ngrx推荐的最佳实践
你之前可能踩的坑
如果之前你尝试在同一个Effect里先dispatch更新动作,再立即select状态,很可能会拿到旧数据(因为动作分发和状态更新是同步的,但流的处理是异步的),甚至因为数据为空导致流中断,返回undefined触发TypeError。比如这种错误写法:
// ❌ 错误示例:同一个Effect里dispatch后立即select,可能拿到旧数据 wrongEffect$ = createEffect(() => this.actions$.pipe( ofType(someTriggerAction), tap(() => this.store.dispatch(updateDataSuccess({ data }))), switchMap(() => this.store.select(selectCurrentData)), // 这里可能拿到初始状态null switchMap(data => this.apiService.call(data)) // data为undefined,触发错误 ) );
按照上面的方案来调整,应该就能解决你的TypeError,同时完美实现顺序执行两个动作的需求啦!
内容的提问来源于stack exchange,提问作者Kiran
相关产品推荐
相关产品推荐

