【问题标题】:If "keyof (A|B)" is "never", why is "excess property checking" happy when I use a key from A or B?如果“keyof (A|B)”是“从不”,为什么当我使用来自 A 或 B 的密钥时“过度属性检查”会很高兴?
【发布时间】:2020-07-11 09:47:04
【问题描述】:

我正在尝试更深入地了解 Typescript 类型,但我发现以下行为令人困惑:

interface Person {
    name: string;
}
interface Lifespan {
    birth: number;
    death?: number;
}

let k: keyof (Person | Lifespan); //k is never
let test1: Person | Lifespan = { randomKey: 123 }; //I understand the error: Object literal may only specify known properties (because keyof is never)
let test2: Person | Lifespan = { name: "A Name" }; //No error given which is confusing, where does test2 get "name" property from?

这是否是一种疏忽/试图以某种方式提供帮助 - 通过过多的属性检查?当 union 的 keyof 为 never 时,我不明白为什么 TypeScript 认为“name”在这里有特殊含义。

【问题讨论】:

  • 这是一个联合类型 - test2 可以是 PersonLifespan(所以 { name: "A Name" } 在这里有效)。 keyof (Person | Lifespan) never 因为它们没有共同的属性
  • 感谢您的回答,但我认为在 TS 类型系统的深处将联合视为 OR 有点简单化。它也可以是“两者”,所以 let test2: Person | Lifespan = { name: "asd", birth: 3 }; 也是有效的(合并两者的键),所以 TS 识别出 Person | Lifespan 组合 可以 有名字、出生、死亡,但是如果我们查询那个的键在类型系统中键入,它永远不会提供 - 但是过多的属性检查以某种方式知道这些键 - 这是我觉得令人困惑的地方。
  • 不,联合是or。相关stackoverflow.com/questions/38628115/…。更多信息在这里typescriptlang.org/docs/handbook/unions-and-intersections.html。两者都是交集。关于let test2: Person | Lifespan = { name: "asd", birth: 3 }; - 由于结构类型系统,它是允许的。
  • 这是我试图建立直觉的“结构类型系统”。这就是为什么我要追求明显的不一致之处,因为我相信它们暴露了我直觉中的漏洞。当我看到| 时,我没有想到“或”,而是想到了集合论,因为它更适合 TS 解释事物的方式。例如type X = unknown | number; 是未知的,而不是unknown OR number,因为未知集消耗数字。但是,对于对象属性,我的设置类比和您的 OR 类比都不起作用。 “因为 STS 才允许这样做”有点矫揉造作——无意冒犯。
  • 如果T 可分配给U 的至少一个组成部分,则类型T 可分配给联合类型U。回到你的例子 - { name: "asd", birth: 3 } 可分配给 PersonLifespan 希望有帮助

标签: typescript


【解决方案1】:

这个问题已经有一段时间了,但我希望这个答案仍然有用。我对 TypeScript 的实现不是很熟悉,但是你提到你想提高你对类型的直觉,所以也许这仍然有帮助。

首先,| 运算符可以被视为or 或联合(集合),从代数的角度来看,这两种解释都是兼容的。请记住,集合AB 的并集是包含A 中的每个元素的集合 中的B

x ∈ A ∪ B ≡ {x : x ∈ A ∨ x ∈ B}

是逻辑“或”。

同样,给定值的类型为A | B,当且仅当它为A 类型B。已经包含了“或两者”的情况,因为这是逻辑“或”的定义;是包含的或,而不是独占的。所以A | BAB 类型的联合,因为它是根据逻辑or 定义的。

回到 TypeScript。 the spec 中介绍了当我们声明联合值或观察这样的值时类型检查器会做什么。它有一段时间没有更新,但基本内容是相同的。 §3.4 涵盖联合类型,§3.11.1 涵盖明显成员,这对于了解联合“看起来像”的用途非常有用。

当我们声明一个联合类型的变量并为其赋值时,TypeScript 将检查该值是否可以分配给联合的任何组成类型(或两者,但有不用说):

let t1: Person | Lifespan = { name: '' }; // OK
let t2: Person | Lifespan = { death: 0 }; // Error
let t3: Person | Lifespan = { name: '', birth: 0 }; // Also OK
let t4: Person | Lifespan = { hungriness: 100 }; // WAT? (Error)

检查器接受t1t3 的值,因为这些对象的类型可以分配给至少一个组成部分。对于t2,至少缺少birth 属性并且t3 不能分配给它们中的任何一个。 TypeScript 可以执行多余的属性检查,因为在示例中,我们试图从 fresh 对象进行分配。

但是当我们尝试“消费”它们时,当我们观察这种类型时,这些类型“看起来”不同。例如:

function foo(param: Person | Lifespan) {
  let name = param.name; // Error
}

这里的错误是非常合理的。我们正在尝试访问仅存在于其中一种成分类型中的属性,但 param 被声明为联合,我们不知道(不检查)哪个是正确的值(或者该值是否具有两者的属性)。我们正在观察该联合的类型和 明显成员 是存在于联合的 每个 组成部分中的那些属性,否则访问是不安全的。 PersonLifespan 类型没有共同的键,因此 TypeScript 不会让我们访问它们中的任何一个。这些类型看起来很像交集 (A & B)。

让我们考虑下一个sn-p:

type T1 = keyof (Person | Lifespan);
type T2 = keyof Person & keyof Lifespan;
type T3 = keyof (Person & Lifespan);
type T4 = keyof Person | keyof Lifespan;

这里我们也观察这些类型,所以keyof (Person | Lifespan) 寻找那些明显的成员但没有找到。在示例中T1T2 都是never,确认当您观察联合类型时,它“看起来很像”一个交叉点。不过,不要把这句话当回事。规范确实更严格地解释了这一点。哦,T3T4 类型都是 "name" | "birth" | "death"

对于交集类型,情况与以下类似:T 类型可分配给如果T 可分配给每个,则交集类型A & B其中,A B。但交集类型A & B 可分配给类型T,如果任何 个,A B,可分配给T

希望这有助于澄清一点。

【讨论】:

    【解决方案2】:

    我认为您在这里混淆了类型的两个方面:它是如何创建 与它是如何使用

    联合类型A | B 比其部分更容易创建。您可以从PersonLifespan创建一个Person | Lifespan;哪个没关系。但它更难使用:任何采用Person | Lifespan 的代码都必须处理这两种情况,而不仅仅是一种。

    其他类型也可以这样理解。交集类型A & B 更难创建但更易于使用(与联合相反); null 类型很容易创建但没有用; never 类型是不可能创建的,但可以用作任何东西。

    在这个框架中,我们可以将keyof 理解为一个关于如何使用类型的声明。因此,当应用于联合类型Person | Lifespan 时,问题不是

    PersonLifespan 上有哪些可用的属性?

    但是

    如果我知道一个值是PersonLifespan,但我不知道是哪个,那么我保证具有哪些属性?

    当然,这个问题的答案是什么都没有。所以keyof (Person | Lifespan)never

    【讨论】:

      猜你喜欢
      • 2014-03-29
      • 2021-11-07
      • 1970-01-01
      • 2018-11-22
      • 2019-10-27
      • 2023-01-14
      • 2021-10-06
      • 2011-05-30
      • 2020-01-03
      相关资源
      最近更新 更多