如何创建可根据参数数据类型推导正确属性的泛型函数?
解决TypeScript联合类型Lookup数据的属性访问错误
我有一些主/静态数据,可以通过ID从这些数据中获取名称,进而推导Entity中的各类属性。我想创建一个泛型函数,根据传入的Lookup数据类型,使用对应属性提取数据,但当前实现因为使用Lookup数据联合类型出现了以下错误:
type LookUpDataTypeA = { calenderId: number; calender: string; desc: string; }; type LookUpDataTypeB = { timeZoneId: number; timeZoneValue: string; desc: string; }; type Entity = { id: number; isActive: boolean; timeZoneId: number; timeZone?: string; calender?: string; calenderId: number; }; const createEntity = ( id = 1, isActive = true, timeZoneId = 10, calenderId = 14 ): Entity => ({ id, isActive, timeZoneId, calenderId, }); // 修正原代码类型错误:应为Entity[] const someEntities: Entity[] = [ createEntity(), createEntity(2, false, 12, 15) ]; const getNamebyId = (data: Entity, lookUpData: (LookUpDataTypeA | LookUpDataTypeB)[]) => { return lookUpData.find(item => item.calenderId === data.calenderId)?.calender }
错误信息:
类型“LookUpDataTypeA | LookUpDataTypeB”上不存在属性“calenderId”。
类型“LookUpDataTypeB”上不存在属性“calenderId”。类型“LookUpDataTypeA | LookUpDataTypeB”上不存在属性“calender”。
类型“LookUpDataTypeB”上不存在属性“calender”。
解决方案1:使用类型守卫区分联合类型
通过自定义类型守卫函数,让TypeScript明确当前处理的Lookup类型,从而安全访问对应属性:
// 类型守卫:判断是否为LookUpDataTypeA function isLookUpDataTypeA(item: LookUpDataTypeA | LookUpDataTypeB): item is LookUpDataTypeA { return 'calenderId' in item; } // 类型守卫:判断是否为LookUpDataTypeB function isLookUpDataTypeB(item: LookUpDataTypeA | LookUpDataTypeB): item is LookUpDataTypeB { return 'timeZoneId' in item; } // 修改后的查找函数,支持两种Lookup类型的查询 const getNameById = (data: Entity, lookUpData: (LookUpDataTypeA | LookUpDataTypeB)[]): string | undefined => { // 查询日历名称 const calenderMatch = lookUpData.find(item => isLookUpDataTypeA(item) && item.calenderId === data.calenderId); if (calenderMatch) return calenderMatch.calender; // 查询时区名称 const timeZoneMatch = lookUpData.find(item => isLookUpDataTypeB(item) && item.timeZoneId === data.timeZoneId); if (timeZoneMatch) return timeZoneMatch.timeZoneValue; return undefined; };
解决方案2:泛型函数实现通用查找(更符合需求)
定义通用的Lookup类型,通过泛型参数关联Entity的ID字段和Lookup的名称字段,实现可复用的类型安全查找:
// 通用Lookup类型:绑定ID字段和名称字段 type GenericLookup<IdField extends string, NameField extends string> = { [K in IdField]: number; } & { [K in NameField]: string; } & { desc: string }; // 基于通用类型重新定义原有Lookup类型 type LookUpDataTypeA = GenericLookup<'calenderId', 'calender'>; type LookUpDataTypeB = GenericLookup<'timeZoneId', 'timeZoneValue'>; // 泛型查找函数:支持任意匹配的ID和名称字段 const getLookupNameById = <IdField extends keyof Entity, NameField extends string>( entity: Entity, lookUpData: GenericLookup<IdField, NameField>[], idField: IdField, nameField: NameField ): string | undefined => { const targetId = entity[idField]; const matchItem = lookUpData.find(item => item[idField] === targetId); return matchItem?.[nameField]; }; // 使用示例 const testEntity = createEntity(); const calenderLookups: LookUpDataTypeA[] = [{ calenderId: 14, calender: "公司工作日历", desc: "周一至周五上班" }]; const timeZoneLookups: LookUpDataTypeB[] = [{ timeZoneId: 10, timeZoneValue: "Asia/Shanghai", desc: "中国标准时间" }]; // 获取日历名称 const calenderName = getLookupNameById(testEntity, calenderLookups, 'calenderId', 'calender'); // 获取时区名称 const timeZoneName = getLookupNameById(testEntity, timeZoneLookups, 'timeZoneId', 'timeZoneValue');
错误原因说明
原代码中,TypeScript无法确定lookUpData.find()返回的是LookUpDataTypeA还是LookUpDataTypeB,而联合类型的两个成员并不共享calenderId或calender属性,因此直接访问这些属性会触发类型错误。通过类型守卫或泛型约束,我们可以让TypeScript明确当前处理的类型,确保属性访问的安全性。
内容的提问来源于stack exchange,提问作者user1264429
相关产品推荐
相关产品推荐

