TypeScript interface 隐式索引签名丢失问题
2024-09-20 • 3min
最近遇到一个奇怪的问题,interface 定义了一个符合要求的类型,却无法作为泛型参数。
ts1type Foo = Record<string, unknown> 2 3type Test<K extends Foo> = K 4 5interface Bar { 6 'a': string 7} 8 9/** 10 * error: 11 * Type 'Bar' does not satisfy the constraint 'Foo'. 12 * Index signature for type 'string' is missing in type 'Bar'. 13 */ 14type Res = Test<Bar>
TS 提示 Bar 类型不满足 Foo 的定义,这本质是一个隐式索引签名丢失的问题。
什么是隐式索引签名
隐式索引签名(implicit index signatures)
是 TypeScript 的一个特性,允许你定义一个对象类型,可以使用任意字符串作为键。
这意味着你可以创建一个对象,在这个对象中,键的名称是动态的,而值的类型是统一的。
例如
ts1type Bar = { 2 [key: string]: number 3} 4const bar: Bar = { 5 'a': 1, 6 'b': 2 7}
解决方案
隐式索引丢失的问题仅限 interface,type 没有这个问题。
也就是说开头的例子,将 Bar 的定义修改为 type 即可:
ts1type Foo = Record<string, unknown> 2 3type Test<K extends Foo> = K 4 5type Bar = { 6 a: string 7} 8 9type Res = Test<Bar>
如果你的类型不便修改,例如它是定义在 node_modules 中的库类型,你也可以使用Pick
将 interface 转换为 type。
ts1type Foo = Record<string, unknown> 2 3type Test<K extends Foo> = K 4 5interface Bar { 6 a: string 7} 8 9type Res = Test<Pick<Bar, keyof Bar>>
interface 这种行为其实符合 TypeScript team 的预期,相关讨论在 2017 年就已经有了。
https://github.com/microsoft/TypeScript/issues/15300
官方解释是: 「interface 可以通过同名接口合并进行增强,而 type 不可以,所以为 type 进行隐式索引推断会更安全」