来自纯粹健全性 https://www.typescriptlang.org/docs/handbook/type-compatibility.html#a-note-on-soundness从角度来看,作业是正确的并且in算子缩小 https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing是不正确的。从务实的角度来看,in
运算符缩小非常有用,而赋值则不然。这两个功能不匹配,因此您提供了一个很好的 TypeScript 代码示例,对于该示例,类型纯粹主义者和惯用的 JS 编码人员都不会对该语言的行为特别满意。
首先我们看一下任务
let x: T = {num1: 1, num2: 2, str1: 'hello'};
根据结构类型 https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html#structural-type-system,初始化器x
绝对是一个类型的值T
,也是如此{num1: 1, num2: 2, str1: true, randomThingy: "x"}
。额外的属性不会违反类型。
但 TypeScript 确实执行超额财产检查 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks作为一种 linter 规则来捕获人们丢弃类型信息的情况。当你分配时const x: {a: string} = {a: "", b: 0}
,你正在丢弃有关的信息b
属性,因为编译器无法恢复它;编译器会忘记b
完全。另一方面,const y = {a: "", b: 0}; const x: {a: string} = y;
很好,因为即使x
不知道b
, y
仍然如此。多余的属性检查规则是对实用主义的认可,而不是类型安全。
这条规则有时会让人们认为 TypeScript 中的对象类型不允许额外的属性,并且它们是 中所要求的“精确类型”微软/TypeScript#12936 https://github.com/microsoft/TypeScript/issues/12936。但 TypeScript 没有精确的类型,并且多余的属性检查规则并没有在人们认为应该执行的任何地方执行。
其中一个地方是当您分配给类似的类型时T
, a 联合型 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types这不是一个受歧视联盟 https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions。在执行多余的属性检查之前,编译器不会将联合拆分为其成员。只要联合体中至少有一个成员需要某个属性,编译器就会对此感到满意。有一个长期悬而未决的问题微软/TypeScript#20863 https://github.com/microsoft/TypeScript/issues/20863要求改变这一点,但在可预见的未来,情况就是这样。
因此,如果您主要关心类型安全,则分配很好,但如果您关心确切类型,则分配不好。
现在我们来看看缩小范围
if('str1' in x) {
console.log(x.str2.toUpperCase());
}
这显然不是健全的行为。毕竟,TypeScript 中的对象类型并不精确,所以如果您知道"str1"
是一个关键x
你不能从逻辑上得出结论"str2"
也是一把钥匙。如果你非常关心类型安全,这是非常不幸的。
另一方面,它非常有用。即使你在技术上不能这么说"str1"
绝对意味着存在"str2"
,它们之间的相关性可能非常强。一般来说,当人们检查仅已知存在于工会一名成员的财产时,他们这样做是因为这确实让他们在实践中(如果不是理论上)歧视工会。
In 微软/TypeScript#15256 https://github.com/microsoft/TypeScript/pull/15256,实施的拉取请求in
算子缩小,有TS 团队的开发负责人发表了以下评论 https://github.com/microsoft/TypeScript/pull/15256#discussion_r154843152:
我们考虑了健全性方面,它与围绕具有未声明属性的别名对象的现有健全性问题没有太大不同。 ...
现实情况是,大多数联合已经正确脱节,并且别名不足以体现问题。有人写一个in
测试不会写出“更好”的检查;实践中真正发生的只是人们添加类型断言或将代码移动到同样不健全的用户定义类型谓词。在网上,我认为这并不比现状更糟糕(而且更好,因为它减少了用户定义的类型谓词,如果由于缺乏拼写检查而使用 in 编写,则很容易出错)。
So in
操作员缩小范围绝对是实用主义战胜稳健性的一个例子。
就这样……务实地说,没有人会为它分配一个奇怪的跨联盟值x
,如果编译器能够在您这样做时发出警告,那就太好了。从技术上讲,使用它是错误的k in o
暗示除某个键之外的任何内容k
,编译器不应该这样做。
但退一步来说,这只是really对于纯粹主义者来说是一个问题。 TypeScript 的类型系统并不完全健全,也无意如此(并且尝试使用编译为 JavaScript 的东西来做到这一点是一个“傻瓜差事” https://github.com/microsoft/TypeScript/issues/9825#issuecomment-306272034根据同一开发负责人的说法)。这TypeScript 的第一个设计目标 https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals是为了捕捉可能的错误,显然很少有人像这个例子那样不小心搬起石头砸自己的脚。
语言的一部分偏向于健全性,而另一部分则偏向于实用主义,这一事实可以被视为语言设计的实用主义:健全性在帮助人们编写无错误程序方面是伟大的,但如果它会被其他东西所取代,那么它就会被抛弃。它带来的伤害大于帮助。再次引用 https://github.com/microsoft/TypeScript/issues/9825#issuecomment-350921122:
最终,类型系统是我们创建的工具,用于帮助我们编写正确的程序。可证明的稳健性是代理措施为了这个努力。如果您的程序在构造上仅在运行时观察到正确的类型,那么计算机是否可以证明这种情况是该短语的两种含义的学术练习。
这意味着,如果您希望看到此示例发生一些变化,您就必须证明此类错误比 TS 团队想象的更常见。通常使用来自运行其中的流行包的真实代码示例。