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

如何将泛型类型传入泛型Selector?Typescript实体状态查询问题

解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 12:37:36