Record
有两种行为,它总是让人们感到困惑,但界面和索引签名也让人们感到困惑.
type Record<K extends string | number | symbol, T> = { [P in K]: T; }
当K
是string
、number
或symbol
的子类型的并集(如'A' | 'B'
)时,它会产生对象文字类型,如下所示:
{ A: string, B: string }
对象文字类型表示:
我知道这里列出的字段,但可以有任意数量的额外字段,并且可以是任何类型
当K
恰好是string
、number
或symbol
的并集(如string | number
)时,它会产生类型签名,如下所示
{
[key: string]: string
[key: number]: string
}
类型签名的基本内容是:
与这种类型的键匹配的每个可能的字段都将属于这种类型
这是非常不同的.
Record
也有点奇怪,因为Record<any, unknown>
的计算结果是{[k: string]: unknown}
,而不是Record<PropertyKey, unknown>
或{ [P in any]: unknown }
,但这并不是你感到惊讶的原因.
底线是:当您 Select 一个对象文字类型或一个索引签名时,这根本不是一种判断.
第二件令人困惑的事情是:接口不是有限的,因为declaration merging.
interface TestInterface {
A: string
}
interface TestInterface {
B: number
}
// same as
interface TestInterface {
A: string
B: number
}
如果您可以随意合并它,甚至使用module augmentation,那么很难说接口在任何给定时间都扩展了索引签名--至少这是我的心理模型,它工作得很好.
唯一的例外是,如果您显式地将接口约束到特定的索引签名:
interface TestInterface {
[k: string]: string
A: "foo"
}
interface TestInterface {
B: number
// ~~~~~~~~~ nope!
}
然后你就可以做出promise 了:如果你是extends Record<any, infer V>
,V
正好是string
(即使A
是"foo"
)
现在,正如 comments 中所提到的,当您编写keyof TestInterface
时,您将获得接口的密钥.在Record
中使用它们将返回Object文字类型,这样您就很好了.
interface TestInterface {
A: string
}
type S = TestInterface extends Record<keyof TestInterface, infer V> ? V : never; // string
只需注意,只有当接口没有显式索引签名时才会出现这种情况:如果接口有显式索引签名,则它不会返回已知键,如"A"
,而是类似string | number
的值(当键为string
时,您总是得到number
),这将在反馈给Record
时返回索引签名.