这是一个我什至无法分类的问题,但让我们尝试一下。考虑以下映射类型定义:
type FeaturesMapEx<T> = {
[K in keyof T]?: T[K];
};
这是有效的,通用映射类型,它使用通用类型T
和T
的字段(键)作为自己的索引类型。这些字段具有各自的T[K]
类型的值,基本上可以转换为
”“您有一个通用的FeatureMapEx类型T
。使用T
获取它的字段并将其用作您自己的字段。如果T[K]
是类型{{1} },则X
是同一类型“
好的。现在我还不太明白,为什么声明不需要像下面那样,使用两个泛型参数,一个代表类型,第二个代表这些类型的键:
FeaturesMapEx<T>[K]
以上声明有效,但未使用K,export type FeaturesMapEx<T, K extends keyof T> = {
[K in keyof T]?: T[K];
};
使用的[K in keyof T]
与通用声明中定义的不同。
甚至更好(但无效)的声明对我来说更常识:
K
哪个会给出typecritp错误,如下所示:
类型文字中的计算属性名称必须引用表达式 其类型是文字类型或“独特符号”类型。ts(1170)
“ K”仅表示类型,但在此处用作值。ts(2693)
虽然对映射类型和泛型的使用感到满意,但是当我在休息一下之后必须自己编写时,这个示例总是让我有些头晕,但我总是陷入后来提出的模式(两个泛型或“类型用作值”错误)...谁能解释我的困惑是普遍的还是我缺少什么?
奖励问题是,export type FeaturesMapEx<T, K extends keyof T> = {
[K]?: T[K];
};
类型从哪里进入第一个声明,如果泛型类型参数未提供它?
感谢您的帮助,谢谢。
基本上,这是召唤提香·切尔尼科娃·德拉戈米尔的陷阱:)
答案 0 :(得分:3)
考虑界面
interface Foo {
a: string,
b: number
}
export type FeaturesMapEx<T> = {
[K in keyof T]?: T[K];
};
在这种映射类型中,我们引入一个新的类型参数K
(它来自哪里),它是在keyof T
上迭代 的。假设keyof T
是联合类型,K
在该联合的每个组成部分上依次迭代。您可以将其视为以下运行时for
循环的类型级别版本:
function featuresMapEx(t: any) {
for (let k of Object.keys(t)) {
t[k];
}
}
请注意,如何在函数签名中引入t
(如类型别名定义中的T
),而在循环中引入k
则仅存在于该循环的范围内(像K
一样是在映射类型键中引入的,并且仅存在于映射类型属性的范围内。)
所以FeaturesMapEx<Foo>
变成{a: string, b: number}
。
如果我改写了以下JavaScript函数:
function featuresMapEx(t: any, k: any) { // unused parameter here
for (let k of Object.keys(t)) { // oops
t[k];
}
}
您会发现,您完全忽略了传递给函数的k
,就像
export type FeaturesMapEx<T, K extends keyof T> = { // unused parameter here
[K in keyof T]?: T[K]; // oops
};
忽略传递到K
的{{1}}。
因此,这个FeaturesMapEx<T, K>
仍然变成FeaturesMapEx<Foo, 'a'>
。
关于您的其他语法:
{a: string, b: number}
这根本不再是映射类型,而是index signature,并且您当前不能使用arbitrary key types in an index signature(尽管有时可能会在TS3.5之后发生)。映射类型看起来像索引签名,但不是一个。即使最终成为该语言的一部分,也可能不是一回事……它可能会像这样:
export type FeaturesMapEx<T, K extends keyof T> = {
[K]?: T[K]; // error
};
也就是说,它不会真正遍历任何东西。您现在可以像这样获得这种行为:
function featuresMapEx(t: any, k: any) {
t[k]; // just one thing
}
与映射类型相同
export type FeaturesMapEx<T, K extends keyof T> = Partial<Record<T, K>>
但此export type FeaturesMapEx<T, K extends keyof T> = {
[P in K]?: T[K]
}
变为FeaturesMapEx<Foo, keyof Foo>
。也就是说,键是正确的,但是它们的值将是您期望的值的并集。
无论如何,我希望与运行时功能的比较可以帮助您“单击”。祝你好运!
答案 1 :(得分:1)
...如果泛型类型参数未提供,那么类型K会从何处进入第一个声明?
在您的第一个声明中,循环中引入了类型K
。
type FeaturesMapEx<T> = {
[K in keyof T]?: T[K]; // this is the loop
};
这是一个使用JavaScript函数的宽松类比:
const someObj = {
foo: 'foo-value',
bar: 'bar-value',
baz: 'baz-value',
}
function featuresMapEx(T) {
return Object.keys(T).map(K => `${K}: ${T[K]}`);
}
const result = featuresMapEx(someObj);
console.log(result);
答案 2 :(得分:1)
从技术上讲,这里没有“为什么”,这只是TS语法的工作方式,我们只需要学习它即可。
表达式[K in keyof T]
包含两个 type运算符。
keyof T
,索引类型查询运算符,它很容易理解,只需要推断接口的键即可。{ [/* one type param */ in /* some unique symbol union type value */]: /* some type value */}
是的,您看得对,这整个过程,包括一对{}
和[]
,一列:
,一个in
关键字,可以看作是< em>类型运算符适用于两个类型值操作数。从K
开始,此处是参数,而不是值。
(实际上,我不确定是否可以再调用这种东西运算符?更像是流控制结构,无论哪种方式,它都是固定的东西,在语法和用法上都有规则) < / p>
再说一遍。您无法向其添加其他成员,这会给您带来语法错误。
type foo<T> = {
[K in keyof T]: boolean;
other: boolean; // <-- syntax error
}
但是,作为人类,我们需要从概念上理解“为什么”!我有一个模拟给你。将通用类型参数视为“自由度”或自变量。
如果一条信息独立于其他信息而存在,则可以视为通用参数。因此,在您的示例中:
type foo<T, K extends keyof T> = {
[K in keyof T]?: T[K];
};
K
不适合作为参数,可以从K
得出关于T
的所有信息,它具有0个自由度。