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

TypeScript泛型场景:为何Interface报错,Type Alias可行?

问题

在TypeScript开发中,我实现了泛型函数formattedGroupByKey用于数组按键分组。最初将数据类型定义为接口I_SeoulParkAPI,调用该泛型函数时出现类型错误;改为类型别名后问题解决。想具体了解为何接口无法适配该泛型场景,而类型别名可以。

初始接口与泛型函数

interface I_SeoulParkAPI {
  AREA: string; //부지 면적
  GUIDANCE: string; // 안내도
  G_LATITUDE: string; // Y좌표(GRS80TM)
  G_LONGITUDE: string; //X좌표(GRS80TM)
  LATITUDE: string; // Y좌표(WGS84)
  LONGITUDE: string; //X좌표(WGS84)
  MAIN_EQUIP: string; // 주요시설
  MAIN_PLANTS: string; // 주요식물
  OPEN_DT: string; // 개원일
  P_ADDR: string; // 주소
  P_ADMINTEL: string; //전화번호
  P_IDX: string; // 연번(공원번호)
  P_IMG: string; // 이미지
  P_LIST_CONTENT: string; // 공원개요
  P_NAME: string; // 관리부서
  P_PARK: string; // 공원명
  P_ZONE: string; // 지역
  TEMPLATE_URL: string; // 홈페이지 바로가기 url
  USE_REFER: string; // 이용시 참고사항
  VISIT_ROAD: string; // 오시는길
}

export function formattedGroupByKey<T extends { [key: string | number]: unknown }>(array: T[], key: keyof T) {
  return array.reduce((acc, cur) => {
    const stringkey = cur[key] as string;
    if (acc.get(stringkey)) {
      acc.get(stringkey)?.push(cur);
    } else {
      acc.set(stringkey, [cur]);
    }
    return acc;
  }, new Map<keyof T, T[]>());
}

报错的调用代码

export const getStaticProps = (async () => {
  const results = await searchSeoulParkInfo('SearchParkInfoService/1/135/');
  const data: I_SearchParkInfoService = results.data;

  return { props: { seoulParkInfo: data.SearchParkInfoService.row } };
}) satisfies GetStaticProps<{
  seoulParkInfo: I_SeoulParkAPI[];
}>;
const MapPage = ({ seoulParkInfo }: { seoulParkInfo: I_SeoulParkAPI[] }) => {
  console.log(seoulParkInfo);
  // 데이터를 해시맵으로 정형화 함
  const formattedData = formattedGroupByKey<I_SeoulParkAPI>(seoulParkInfo, '');

修复后的类型别名

export type I_SeoulParkAPI = {
  AREA: string; //부지 면적
  GUIDANCE: string; // 안내도
  G_LATITUDE: string; // Y좌표(GRS80TM)
  G_LONGITUDE: string; //X좌표(GRS80TM)
  LATITUDE: string; // Y좌표(WGS84)
  LONGITUDE: string; //X좌표(WGS84)
  MAIN_EQUIP: string; // 주요시설
  MAIN_PLANTS: string; // 주요식물
  OPEN_DT: string; // 개원일
  P_ADDR: string; // 주소
  P_ADMINTEL: string; //전화번호
  P_IDX: string; // 연번(공원번호)
  P_IMG: string; // 이미지
  P_LIST_CONTENT: string; // 공원개요
  P_NAME: string; // 관리부서
  P_PARK: string; // 공원명
  P_ZONE: string; // 지역
  TEMPLATE_URL: string; // 홈페이지 바로가기 url
  USE_REFER: string; // 이용시 참고사항
  VISIT_ROAD: string; // 오시는길
};
原因分析

核心差异在于TypeScript中接口和类型别名的类型兼容性规则,尤其是在泛型约束匹配场景下的表现:

  • 接口的扩展性特性
    接口是可扩展的,TypeScript允许后续对同一个接口名称进行补充声明(声明合并)。这意味着TypeScript会把接口视为「可能拥有更多属性的开放类型」,而非精确的固定结构。
    你的泛型函数约束T extends { [key: string | number]: unknown }要求T是一个索引签名类型(可通过字符串/数字键访问属性),但接口即使定义了所有明确属性,TypeScript也不会自动将其视为满足索引签名——因为接口可能被后续扩展,新增非字符串类型的属性,破坏约束的安全性。

  • 类型别名的精确性特性
    类型别名是封闭的,一旦定义就无法被扩展。TypeScript会将类型别名视为精确的固定结构,当类型别名的所有属性值都是string类型时,它会自动匹配泛型的索引签名约束——因为不存在后续修改、新增非兼容属性的可能。

  • 泛型返回值的类型匹配
    泛型函数返回Map<keyof T, T[]>,使用接口时,keyof I_SeoulParkAPI是所有明确属性名的联合类型,但由于接口的开放性,TypeScript无法确定这些属性的类型是否都能被安全转换为string(即便代码里用了as string断言,类型检查阶段仍会质疑这种转换的合理性)。而类型别名的keyof结果是明确的固定联合类型,所有属性值都是string,类型检查会认可这种转换。

简单来说:接口是开放的,TypeScript会假设它可能被扩展,因此不满足泛型的索引签名约束;类型别名是封闭的,结构固定,完全匹配泛型约束的要求。

内容的提问来源于stack exchange,提问作者최문길

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:42:34