logo科技微讯

Typescript 报错 index signature is missing 的原因和解决方法

作者:科技微讯
日期:2023-03-11
📝 笔记

写 typescript 的过程中我也遇到了 Index signature is missing in type xxxx 报错,不知道哪里出问题,直到我无意间把类型声明从 interface 改为 type alias,报错消失。

为什么会这样?谷歌找到 Typescript Github repo 的一个 issue,开发者在这里解释了出现这个报错的原因。

举个例子:

//定义一个 index interface
interface Test {
  [key: string]: string;
}
//用 interface 的方法定义一个类型
interface Value1 {
  name: string;
}
//用 type alias 定义一个类型,看起来和前面一个是一样的
type Value2 = { name: string };

//声明两个变量,分别使用第二、第三个类型
const value_1: Value1 = { name: "科技微讯" };
const value_2: Value2 = { name: "科技微讯" };

//声明一个变量,使用第一个 index interface
let test: Test;

//报错:Index signature for type 'string' is missing in type 'MyInterface'.
//即 Value1 缺少了 Test 的 [key: string]: string;
test = value_1;
//type alias 正常,即 Value2 不会被报错缺少 Test 的 [key: string]: string;
test = value_2;

之所以会这样,是因为

Just to fill people in, this behavior is currently by design. Because interfaces can be augmented by additional declarations but type aliases can't, it's "safer" (heavy quotes on that one) to infer an implicit index signature for type aliases than for interfaces. But we'll consider doing it for interfaces as well if that seems to make sense

上面这段话是 2017 年的,后续不断有开发者反映这个问题,但一直到 2022 年作者表示这一表现没有发生改变,以后也不会:

This has not changed.
Changing the behavior at this point would honestly be an incredibly disruptive breaking change without much concrete upside. For clarity, I'm going to close and lock this - interfaces should declare an explicit index signature if they intend to be indexed.

简单点说,这是 typescript 的一个特性,之所以 interface 会报错而 type alias 不会,是因为 interface 可以多次声明,多次声明会合并成一个:

interface MyInterface {
  name: string;
}
interface MyInterface {
  age: number;
}
const people: MyInterface = {
  name: "tom",
  age: 10,
};

因为可以多次声明,所以 interface 不应该自动 infer an implicit index signature,即如果一个 interface 没有 [key: string]: string;,typescript 不会自动给它 infer 一个。你想想,如果自动 infer 了会怎样?答案是会和后续多次声明冲突,例如:

//假设 Tom 会被 infer an implicit index signature
interface Tom {
  name: string;
}
const tom: Tom = {
  name: "tom",
};
interface Test {
  [key: string]: string;
}

//那这里就会把 Test 的 [key: string]: string; infer 给 Tom
const test: Test = tom;

//相当于 Tom 变成了:
// interface Tom {
//   name: string;
//   [key: string]: string;
// }

//这样的话,当我们重复声明 Tom 时,就会报错,
//因为 age: number 和 [key: string]: string; 冲突
interface Tom {
  age: number;
}

//当然了,事实上重复用 interface 声明 Tom 并不会报错,
//因为 Tom 并不会被 infer [key: string]: string;

解决这个问题的方法有几种,回到刚开始的例子,既然报错是说 Value1 缺少 Index signature,那就给它加一个吧,如果现在还不确定其他 key 是什么类型,可以指定 index signature 为 any 或 unknown:

interface Value1 {
  name: string;
  //增加:
  [key: string]: any;
  //或者:
  //[key: string]: unknown
}

另一种方法是改用 type alias 声明 Value1

type Value1 = {
  name: string;
};

因为 type alias 不能像 interface 那样可以重复声明,所以 type alias 会被 infer an implicit index signature

//当我们这样:
test = value_1;
//Value1 其实被 infer 成这样了:
type Value1 = {
  name: string;
  [key: string]: string;
};
//所以 Test 和 Value1 就不会冲突
donation赞赏
thumbsup0
thumbsdown0
暂无评论