Ngrx 11+中createAction生成Action的类型安全Effect写法咨询
关于管道内Action类型的声明
ReturnType<typeof FooActions.getFoo> 这个写法本身是完全正确的,是NgRx官方推荐的Action类型提取方式,不属于反模式。但你在示例中给map的参数手动声明元组类型的操作是多余的。ofType操作符本身内置了类型重载,当你传入FooActions.getFoo这个Action创建函数后,后续操作符接收到的流值已经被自动推导为ReturnType<typeof FooActions.getFoo>类型,不需要重复声明。你只需要给组合使用的bar$提前标注好类型(比如bar$: Observable<Bar[]>),withLatestFrom组合生成的元组类型会被TypeScript自动推导为[ReturnType<typeof FooActions.getFoo>, Bar[]],完全不需要手动写类型注解。
如果遇到TS版本或NgRx版本兼容导致类型推导失效的场景,手动写ReturnType<typeof 对应Action创建函数>是最稳妥的方案,比你重新手写Action接口要简洁,也能避免后续修改Action props时出现类型定义和实际逻辑不一致的问题。
你提到的用union包裹ReturnType的方案,仅适用于ofType同时传入多个Action创建函数的场景,此时推导出来的是多Action的联合类型,单Action监听场景下不需要使用。
关于Effect实例的类型声明
不需要给onLoadFooBar$手动标注ReturnType<typeof FooActions.loadFoo>类型,这个写法本身就是错误的:createEffect返回的是封装了Action流的Effect实例(本质是特殊的Observable),不是单个Action对象,标注单个Action类型完全不符合类型逻辑。
正常使用场景下,createEffect会自动推导内部管道返回的Action类型,只要你内部操作符最终返回的是createAction生成的合法Action实例,TypeScript会自动完成全链路类型校验,不需要给Effect变量额外加类型注解。如果确实需要显式标注类型,导入NgRx提供的Effect类型,写为onLoadFooBar$: Effect = createEffect(...)即可,不需要手动指定内部Action泛型,泛型会从传入的工厂函数自动推导。
修正后的参考代码
// 仅需要给组合的辅助Observable提前标注类型 bar$: Observable<Bar[]> = this.store.select(selectBarList); const onLoadFooBar$ = createEffect(() => { return this.actions$.pipe( ofType(FooActions.getFoo), withLatestFrom(this.bar$), map(([foo, bar]) => { // foo自动推导为 FooActions.getFoo 对应的Action类型 // bar自动推导为 Bar[] 类型 // 业务逻辑处理 return FooActions.loadFoo({ /* 对应payload参数 */ }); }) ); });
补充说明:当你需要编写工具函数接收特定Action作为参数时,使用
ReturnType<typeof XxxAction>提取类型是标准做法,不要重复定义Action接口,避免维护多份类型定义带来的不一致问题。
内容的提问来源于stack exchange,提问作者Vincent

