TypeScript和非通用类型参数

时间:2019-04-17 18:35:05

标签: typescript

这是一个我什至无法分类的问题,但让我们尝试一下。考虑以下映射类型定义:

type FeaturesMapEx<T> = {
    [K in keyof T]?: T[K];
};

这是有效的,通用映射类型,它使用通用类型TT的字段(键)作为自己的索引类型。这些字段具有各自的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]; }; 类型从哪里进入第一个声明,如果泛型类型参数未提供它?

感谢您的帮助,谢谢。

编辑

基本上,这是召唤提香·切尔尼科娃·德拉戈米尔的陷阱:)

3 个答案:

答案 0 :(得分:3)

考虑界面

interface Foo {
  a: string,
  b: number
}

mapped type

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运算符

  1. keyof T索引类型查询运算符,它很容易理解,只需要推断接口的键即可。
  2. 映射类型,这有点棘手。
{ [/* 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个自由度。