如何通过类型守卫与底层隐式推断推导返回类型的泛型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
相关产品推荐
相关产品推荐

