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

TypeScript中===与Array.includes的类型收窄差异及解决方法

TypeScript类型收窄:===比对与Array.includes的差异及解决方案

代码示例

interface Person {
  target:
    | "first_name"
    | "last_name"
    | "date_of_birth"
    | "address_line"
    | "address_line2"
    | "city"
    | "state"
    | "zip";
}

interface Address {
  address_line?: string | null;
  address_line2?: string | null;
  state?: string | null;
  city?: string | null;
  zip?: string | null;
}

interface Owner {
  id: number;
  first_name: string;
  last_name: string;
  date_of_birth: string | null;
  address: Address
}

// 用===判断的正常版本
function createNewOnwer(target: Person["target"]) {
  const newData = {} as Partial<Owner>;

  if (
    target === "address_line" ||
    target === "address_line2" ||
    target === "zip" ||
    target === "state" ||
    target === "city"
  ) {
    newData.address = {
      [target]: "test address" // TypeScript能准确识别target是地址字段的联合类型
    };
  } else {
    newData[target] = String("test address"); // 这里target是基础信息字段的联合类型
  }
}

// 用Array.includes判断的异常版本
function createNewOnwerWithIncludeCheck(target: Person["target"]) {
  const newData = {} as Partial<Owner>;

  if (
    ["address_line", "address_line2", "zip", "state", "city"].includes(target)
  ) {
    newData.address = {
      [target]: "test address" // ❌ TypeScript无法识别target的具体类型范围
    };
  } else {
    newData[target] = String("test address"); // ❌ TypeScript抛出类型错误
  }
}

// 手动类型断言的临时解决方案版本
function createNewOnwerWithIncludeCheckWithHardcodedTypes(target: Person["target"]) {
  const newData = {} as Partial<Owner>;

  if (
    ["address_line", "address_line2", "zip", "state", "city"].includes(target)
  ) {
    newData.address = {
      [target as "address_line" | "address_line2" | "zip" | "state" | "city"]: "test address" // 仍需手动断言,不够优雅
    };
  } else {
    newData[target as "first_name" | "last_name" | "date_of_birth"] = String("test address");
  }
}

问题解答

1. 为什么===比对能实现类型收窄,Array.includes不行?

TypeScript的控制流分析对字面量比对(===)有特殊处理:当你用target === "xxx"这种字面量相等判断时,TypeScript能直接识别出该分支里target的类型就是对应字面量,多个||组合后,会自动收窄为这些字面量的联合类型。

而Array.includes的问题在于:直接定义的数组["address_line", ...]会被TypeScript推断为string[]类型,includes方法的参数默认是宽泛的string。虽然Person["target"]是string的子类型,但TypeScript无法反向推导——它不知道这个数组里的元素恰好是Person["target"]的一个子分支,因此无法通过includes(target)的判断收窄target的类型,依然会认为target是完整的Person["target"]联合类型,导致后续赋值出现类型不匹配错误。

2. 如何修改代码让Array.includes实现正确的类型收窄?

有两种常用的优雅解决方案:

方案一:使用const断言约束数组类型

把地址字段数组用as const断言,让TypeScript推断出数组元素的字面量联合类型,而非宽泛的string[]。这样includes方法的参数会被约束为该联合类型,TypeScript就能通过判断收窄target的类型:

function createNewOnwerWithIncludeFixed(target: Person["target"]) {
  const newData = {} as Partial<Owner>;
  // 用as const让数组元素类型被推断为字面量联合类型
  const addressFields = ["address_line", "address_line2", "zip", "state", "city"] as const;

  if (addressFields.includes(target)) {
    newData.address = {
      [target]: "test address" // ✅ TypeScript能识别target是地址字段的联合类型
    };
  } else {
    newData[target] = String("test address"); // ✅ 这里target自动收窄为基础信息字段的联合类型
  }
}
方案二:自定义类型守卫函数

写一个类型守卫函数,明确告诉TypeScript当函数返回true时,target属于地址字段的类型:

// 自定义类型守卫,判断target是否为Address的键
function isAddressField(target: Person["target"]): target is keyof Address {
  return ["address_line", "address_line2", "zip", "state", "city"].includes(target);
}

function createNewOnwerWithTypeGuard(target: Person["target"]) {
  const newData = {} as Partial<Owner>;

  if (isAddressField(target)) {
    newData.address = {
      [target]: "test address" // ✅ TypeScript自动收窄target为keyof Address
    };
  } else {
    newData[target] = String("test address"); // ✅ target自动收窄为剩余的联合类型
  }
}

内容的提问来源于stack exchange,提问作者Adam Orłowski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 16:30:47