Yiqing Fan

TypeScript 在 Access 和 Assign Value 时的不同类型推断

2023/08/20

最近遇到了一个有意思的 TypeScript 小问题,让我理解了 TS 在 access 和 assign value 的时候会有不同的类型推断。下面是一个简单的例子:我们现在有一个 object,想要在 runtime 根据 key 的取值来修改对应的 value:

type User = {
  name: string;
  age: number;
};

const user: User = {
  name: "frank",
  age: 24,
};

const key = Math.random() < 0.5 ? "name" : "age";
// key has type "name" | "age"

const value = user[key];
// access value: user[key] has type string | number

user[key] = key === "name" ? "felix" : 23; // TS error: Type 'string | number' is not assignable to type 'never'
// assign value: user[key] has type never

这么写从逻辑上看好像没有问题,但是 TS 会对最后一行的 assignemnt 报错说 Type 'string | number' is not assignable to type 'never'。研究了一下发现同样是推断 user[key] 的类型,但是在 access 和 assign 的情况下,TS 的表现是不同的:

要解决这个 error 的方法也非常简单:// @ts-ignore

if (key === "name") {
  // user[key] has type string
  user[key] = "felix";
} else {
  // user[key] has type number
  user[key] = 23;
}

这样就不会有 error 的原因是 TS 会做 type narrowing,推断出在第一条 branh 上 user[key] 只能是 string,在第二条 branch 上 user[key] 只能是 number

需要注意的是这种 type narrowing 只适用于 explicit control flow,所以这里的 if statement 可以,而一开始的 ternary expression 就不行。