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

如何通过类型守卫与底层隐式推断推导返回类型的泛型T?

问题:TypeScript中通过类型守卫推导泛型返回类型

初始场景:单ID参数的类型推导问题

需求是通过类型守卫和隐式推断,让函数返回类型的泛型T自动匹配输入ID的类型,但现有代码中getDbItem函数返回值类型报错:

type Entity<T> = {payload: T};

interface IBaseDataType {name?: string};
interface IDataTypeV1 extends IBaseDataType {id: number};
interface IDataTypeV2 extends IBaseDataType {id: string};

const dataV1: IDataTypeV1[] = [{id: 1, name: '1'}, {id: 2, name: '2'}, {id: 3, name: '3'}, {id: 4, name: '4'}]
const dataV2: IDataTypeV2[] = [{id: '1', name: '1'}, {id: '2', name: '2'}, {id: '3', name: '3'}, {id: '4', name: '4'}]

const getDbItemV1 = (id: number): Entity<IDataTypeV1> => {
  return {
    payload: {
      id,
      name: dataV1.find(d => d.id === id)?.name
    }
  };
}

const getDbItemV2 = (id: string): Entity<IDataTypeV2> => {
  return {
    payload: {
      id,
      name: dataV2.find(d => d.id === id)?.name
    }
  };
}

const isString = (val: unknown): val is string => typeof val === 'string';

type DataType<T> = T extends string ? IDataTypeV2 : IDataTypeV1;

const getDbItem = <T extends string | number>(id: T): Entity<DataType<T>> 
    => isString(id) ? getDbItemV2(id) : getDbItemV1(id); 
// 报错:Type 'Entity<IDataTypeV1> | Entity<IDataTypeV2>' is not assignable to type 'Entity<DataType<T>>'

解决方案:使用函数重载

TypeScript无法自动将条件分支的返回类型与泛型条件类型DataType<T>关联,通过函数重载可以明确不同参数类型对应的返回类型,同时消除内部类型错误:

type Entity<T> = {payload: T};

interface IBaseDataType {name?: string};
interface IDataTypeV1 extends IBaseDataType {id: number};
interface IDataTypeV2 extends IBaseDataType {id: string};

const dataV1: IDataTypeV1[] = [{id: 1, name: '1'}, {id: 2, name: '2'}, {id: 3, name: '3'}, {id: 4, name: '4'}]
const dataV2: IDataTypeV2[] = [{id: '1', name: '1'}, {id: '2', name: '2'}, {id: '3', name: '3'}, {id: '4', name: '4'}]

const getDbItemV1 = (id: number): Entity<IDataTypeV1> => {
  return {
    payload: {
      id,
      name: dataV1.find(d => d.id === id)?.name
    }
  };
}

const getDbItemV2 = (id: string): Entity<IDataTypeV2> => {
  return {
    payload: {
      id,
      name: dataV2.find(d => d.id === id)?.name
    }
  };
}

const isString = (val: unknown): val is string => typeof val === 'string';

// 重载签名:明确参数类型与返回类型的映射
function getDbItem(id: string): Entity<IDataTypeV2>;
function getDbItem(id: number): Entity<IDataTypeV1>;
// 实现签名:处理逻辑,兼容所有参数类型
function getDbItem(id: string | number): Entity<IDataTypeV1 | IDataTypeV2> {
  return isString(id) ? getDbItemV2(id) : getDbItemV1(id);
}

const getItem = (id: number) => getDbItem(id); // 自动推断为 Entity<IDataTypeV1>
console.log(getItem(3));

扩展场景:双ID参数的类型推导问题

需求是将ID拆分为numberId: number | null和stringId: string | null两个参数,要求numberId非空时返回Entity<IDataTypeV1>,stringId非空时返回Entity<IDataTypeV2>,之前的尝试无法精确推断返回类型:

type BaseTypeMap<T extends object, U extends object> = {
  string: { type: string; return: T };
  number: { type: number; return: U };
};
type TypeMap = BaseTypeMap<IDataTypeV2, IDataTypeV1>;

const getDbItem = <T extends keyof TypeMap>(
  numberId: Extract<TypeMap[T]['type'], number> | null,
  stringId: Extract<TypeMap[T]['type'], string> | null
): Entity<TypeMap[T]['return']> =>
  isString(stringId) ? getDbItemV2(stringId) : getDbItemV1(numberId as number);
// 返回类型无法精确推断为对应类型

解决方案:重载+参数类型守卫

通过重载签名明确有效参数组合,结合类型守卫细化参数类型,实现精确的返回类型推断:

type Entity<T> = {payload: T};

interface IBaseDataType {name?: string};
interface IDataTypeV1 extends IBaseDataType {id: number};
interface IDataTypeV2 extends IBaseDataType {id: string};

const dataV1: IDataTypeV1[] = [{id: 1, name: '1'}, {id: 2, name: '2'}, {id: 3, name: '3'}, {id: 4, name: '4'}]
const dataV2: IDataTypeV2[] = [{id: '1', name: '1'}, {id: '2', name: '2'}, {id: '3', name: '3'}, {id: '4', name: '4'}]

const getDbItemV1 = (id: number): Entity<IDataTypeV1> => {
  return {
    payload: {
      id,
      name: dataV1.find(d => d.id === id)?.name
    }
  };
}

const getDbItemV2 = (id: string): Entity<IDataTypeV2> => {
  return {
    payload: {
      id,
      name: dataV2.find(d => d.id === id)?.name
    }
  };
}

const isString = (val: unknown): val is string => typeof val === 'string';
const isNumber = (val: unknown): val is number => typeof val === 'number';

// 重载签名:指定有效参数组合对应的返回类型
function getDbItem(numberId: number, stringId: null): Entity<IDataTypeV1>;
function getDbItem(numberId: null, stringId: string): Entity<IDataTypeV2>;
// 实现签名:处理所有参数情况,添加错误处理保证类型安全
function getDbItem(numberId: number | null, stringId: string | null): Entity<IDataTypeV1 | IDataTypeV2> {
  if (isString(stringId)) {
    return getDbItemV2(stringId);
  }
  if (isNumber(numberId)) {
    return getDbItemV1(numberId);
  }
  throw new Error("必须提供numberId或stringId中的一个有效值");
}

// 测试:自动推断正确的返回类型
const item1 = getDbItem(3, null); // Entity<IDataTypeV1>
const item2 = getDbItem(null, '3'); // Entity<IDataTypeV2>

内容的提问来源于stack exchange,提问作者Nikolai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:07:05