TypeScript泛型方法返回联合类型访问属性报错如何解决
问题原因
当前方法没有定义类型重载,TypeScript 无法根据「是否传入 select 参数」收窄返回类型,会将两个逻辑分支的返回值合并为 UserSafeData | T 的联合类型。根据TS类型规则,访问联合类型的属性时,要求该属性在所有联合成员上存在,因此访问仅在T中定义的refreshToken字段时会抛出类型错误。
另外原实现的泛型定义没有和入参select做关联,也无法校验传入的select字段和泛型T是否匹配,容易出现声明和实际查询字段不一致的问题。
修复方法
使用TS函数重载能力,分别定义两种入参对应的返回类型,从根本上区分两个场景的类型推导:
import type { UserData } from '你的类型定义路径'; // 重载1:未传入select时,固定返回UserSafeData类型 async findUserById(id: number): Promise<UserSafeData>; // 重载2:传入select时,自动根据选中字段推导返回类型 async findUserById<T extends readonly (keyof UserData)[]>( id: number, select: T ): Promise<Pick<UserData, T[number]>>; // 方法实际实现,类型对内兼容即可,不会对外暴露 async findUserById( id: number, select?: (keyof UserData)[] ) { if (!select) { select = ["id", "name", "email", "picture", "bio"]; const user = await this.findOne(id, { select }); return user as UserSafeData; } const user = await this.findOne(id, { select }); return user; }
方案说明
- 重载签名对外完全隔离了两种入参场景,TS会根据调用时是否传
select参数,自动匹配对应的返回类型,不会再生成联合类型 - 不需要手动传入泛型,返回类型会根据传入的
select数组自动推导,比如传入["id", "refreshToken", "email"]时,返回值自动推导为{ id: number; email: string; refreshToken: string },直接访问refreshToken不会有任何类型错误 - 自动校验select传入的字段合法性,如果你传了不存在于
UserData的字段,TS会直接抛错,提前发现问题 - 不传select调用时,返回值自动推导为
UserSafeData,原有逻辑完全不受影响
如果你确实需要保留手动传入泛型的能力(比如查询后做字段格式化、需要自定义返回类型的场景),可以把第二个重载替换为如下定义即可:
// 重载2:支持手动传入泛型自定义返回类型 async findUserById<T extends Partial<UserData>>( id: number, select: (keyof T)[] ): Promise<T>;
这个定义会自动校验传入的select字段必须是泛型T中存在的字段,避免声明和查询字段不匹配的问题,同时返回值类型固定为T,不会和UserSafeData组成联合类型。
内容的提问来源于stack exchange,提问作者Amr Elmohamady
相关产品推荐
相关产品推荐

