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

如何创建可根据参数数据类型推导正确属性的泛型函数?

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 10:07:04