gpt4 book ai didi

用于合并的 Typescript 通用语法

转载 作者:行者123 更新时间:2023-12-05 04:36:41 24 4
gpt4 key购买 nike

type OptionalPropertyNames<T> =
{ [K in keyof T]-?: ({} extends { [P in K]: T[K] } ? K : never) } [keyof T];

大部分内容我都听懂了,但没听懂:

{} extends { [P in K]: T[K] } ? K : never

那个代码块代表什么?在什么条件下条件为真?

StackOverflow post 中找到语法

最佳答案

OptionalPropertyNames<T> 的目的是找到对应于 optional propertiesT 的键,我们可以看到它有效(至少对于没有 index signatures 的对象类型或其他边缘情况):

type OptionalPropertyNames<T> = { [K in keyof T]-?:
({} extends { [P in K]: T[K] } ? K : never)
}[keyof T];

interface Foo {
opt?: string,
req: string | undefined
}

type Opt = OptionalPropertyNames<Foo> // "opt"

这是通过从 K 中的键查看每个属性 T 并检查是否本质上是 {} extends Pick<T, K>(类型 {[P in K]: T[K]} 至少在 Pick<T, K> 的实例中等同于 the K extends keyof T utility type)来工作的。因此,对于 opt 属性,我们检查 {} extends {opt?: string},对于 req 属性,我们检查 {} extends {req: string | undefined}

对于 {} extends {req: string | undefined} 这显然是错误的。如果你有一个 {} 类型的值,你就不能安全地将它视为一个 {req: string | undefined} 类型的值。但是,对于 {} extends {opt?: string} ,TypeScript 认为它是 true。这让我们能够区分必需属性和可选属性,因此 OptionalPropertyNames<Foo>"opt"

这就是所问问题的答案。但是等等,为什么 {} extends {opt?: string} 是真的?


好吧,允许将没有已知属性的对象类型分配给具有可选属性的对象类型非常方便:

interface Bar {
req: string | undefined
}
let bar: Bar = { req: "req" };

interface Foo extends Bar {
opt?: string;
}
let foo: Foo = bar; // allowed by convenience

如果最后一行失败,那就太烦人了。我们知道 bar 是有效的 Foo,因为 bar 缺少 opt 属性。编译器耸了耸肩说“好吧,类型 Bar 没有已知的 opt 属性,所以这有点像说它缺少一个 opt 属性,它与来自的可选属性兼容Foo”。但这并不是真正的 sound 假设。

TypeScript 中的对象类型是开放可扩展,而不是密封精确(如 microsoft/TypeScript#12936 中所要求的) ),以支持 structural subtyping 。一个对象的属性总是比对象类型中的属性多:

interface Baz extends Bar {
opt: number;
}
let baz: Baz = { opt: 123, req: "req" };
bar = baz; // allowed by structural subtyping

此处一个具有数字 opt 属性的值已分配给 bar ,并且该分配是合法的,并且通过结构子类型化是类型安全的。但是放在一起,这两个规则可能会导致麻烦。虽然编译器理所当然地提示这个分配:

foo = baz; // error! Types of property 'opt' are incompatible.

如果您分两步进行相同的分配,它不会注意到:

bar = baz; // allowed by subtyping
foo = bar; // allowed by convenience

这意味着编译器一切正常,直到运行时你才知道有什么问题:

if (foo.opt) {
foo.opt.toUpperCase(); // no compiler error, but:
// 💥 RUNTIME ERROR: foo.opt.toUpperCase is not a function
}

糟糕。

所以这就是TypeScript的类型系统不一致的地方之一。 (参见 microsoft/TypeScript#42479 。)上面的定义使用它来检测可选属性。


在烦人的替代宇宙中,编译器不允许这样的赋值,你可能仍然可以用类似的东西检测可选属性:

type OptionalPropertyNames<T> = { [K in keyof T]-?:
(Partial<Pick<T, K>> extends Pick<T, K> ? K : never)
}[keyof T];

因为 the Partial<T> utility type 应该产生一些不可分配给 T 的东西,除非 T 已经拥有所有可选属性。但这比上一节更题外话,所以我就到此为止。

Playground link

关于用于合并的 Typescript 通用语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70779738/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com