如何将泛型类型传入泛型Selector?Typescript实体状态查询问题
这个问题我之前也碰到过,本质是TypeScript对联合类型的类型收窄要求——你的Reference是A|B|C的联合类型,直接把Reference[]赋值给A[]肯定过不了校验,因为数组里可能混着B、C类型的对象。下面是一套既能解决类型报错,又能让TypeScript自动校验目标类型是否属于Reference的方案:
1. 先写类型守卫函数收窄类型
首先我们需要一个类型守卫,让TypeScript能识别某个Reference实例是否属于我们要筛选的目标类型。你可以写通用版或者针对每个类型的具体版:
通用类型守卫(灵活)
// 通用守卫:通过检查对象是否包含目标类型的标志性属性来判断 export function isReferenceType<T extends Reference>(obj: Reference, key: keyof T): obj is T { return key in obj; }
具体类型守卫(精确)
如果想更精确,也可以给每个类型单独写守卫:
export function isA(obj: Reference): obj is A { return 'propA' in obj; } export function isB(obj: Reference): obj is B { return 'propB' in obj; } export function isC(obj: Reference): obj is C { return 'propC' in obj; }
2. 改造selectReference为泛型选择器
接下来把你的选择器改成泛型函数,约束泛型参数必须是Reference的子类型,同时加入过滤逻辑让TypeScript确认返回的是目标类型数组:
基于通用守卫的版本(推荐)
这个版本会自动校验你传入的类型是否属于Reference,同时通过过滤确保返回的数组全是目标类型:
import { createSelector } from '@reduxjs/toolkit'; // 替换成你的slice导出的状态选择器和适配器 import { selectRef, adapter } from './reference.slice'; export const selectReference = <T extends Reference>(typeKey: keyof T) => createSelector(selectRef, (state) => { const allReferences = adapter.getSelectors().selectAll(state); // 过滤+类型守卫,让TypeScript识别返回的是T[] return allReferences.filter((item): item is T => isReferenceType(item, typeKey)); });
基于具体守卫的版本
如果用的是具体类型守卫,可以这样写:
export const selectA = createSelector(selectRef, (state) => adapter.getSelectors().selectAll(state).filter(isA) ); export const selectB = createSelector(selectRef, (state) => adapter.getSelectors().selectAll(state).filter(isB) );
3. 在组件中使用泛型选择器
现在你可以在组件里指定目标类型,TypeScript会自动校验这个类型是否属于Reference(比如你如果传D,会直接报错):
import { selectReference } from './reference.selectors'; export class MyComponent { collection$: Observable<Array<A>>; getCollection(): void { // 传入A的标志性属性propA,指定泛型为A this.collection$ = this.store.pipe( select(selectReference<A>('propA')) ); } }
额外优化:更友好的类型推导(可选)
如果你不想每次都写泛型参数,可以用一个映射类型关联类型名称和它的标志性属性,让选择器自动推导类型:
// 定义类型到属性的映射,只包含Reference中的类型 type ReferenceTypeMap = { A: keyof A; B: keyof B; C: keyof C; }; // 改造选择器,用字符串类型推导返回值 export const selectReference = <T extends keyof ReferenceTypeMap>(typeName: T) => createSelector(selectRef, (state) => { const key = ReferenceTypeMap[typeName]; return adapter.getSelectors().selectAll(state).filter((item): item is typeof item & { [K in typeof key]: any } => key in item ) as Array<{ [K in keyof typeof item]: typeof item[K] } & Reference>; }); // 调用时更简洁,TypeScript自动推导返回A[] this.collection$ = this.store.pipe( select(selectReference('A')) );
为什么原来的代码报错?
adapter.getSelectors().selectAll(state)返回的是Reference[](即A|B|C的数组),而你要赋值给Observable<Array<A>>,TypeScript无法保证数组中的每个元素都是A类型,所以会抛出Type 'Reference' is not assignable to type 'A'的错误。我们通过类型守卫+过滤的方式,让TypeScript确信过滤后的数组只包含目标类型的元素,同时泛型约束T extends Reference确保你不能传入不属于Reference的类型(比如D)。
内容的提问来源于stack exchange,提问作者Adrii

