为什么以下接口建立的两种类型是一样的?
interface AB {
a?: string;
b?: string;
c?: string;
}
interface CD {
c?: string;
d?: string;
a?: string;
}
(显示它们可以相互分配的示例是这里)
当然,它们共享属性
a
和 c
,但不共享 b
和 d
。
无论好坏,TypeScript 的类型系统都不是完全类型安全的,或者说声音。尽管 TypeScript 的大部分实用程序都围绕着提高类型安全性,但一些常见的编码模式违反了这种安全性;类型安全性和表现力之间需要权衡;有时,TypeScript 更看重表现力和开发人员的便利性,而不是安全性。请参阅 TypeScript 设计非目标 #5。
其中一个健全性漏洞与可选属性有关。请参阅有关 microsoft/TypeScript#47731 的评论和有关 microsoft/TypeScript#42479 的评论进行演示。
将具有可选属性的对象类型分配给缺少该属性的类型是完全安全的。这只是类型的正常加宽,TypeScript 允许这样做:
let x: { a?: number, b: string } = { a: 1, b: "" };
let y: { b: string };
y = x; // okay
但是 TypeScript 还允许您将缺少属性的对象类型分配给具有相同键的可选属性的类型:
x = y; // okay?!
这在技术上是不安全的缩小。
{b: string}
类型的值可能在 a
键处具有任何属性(这是因为 TypeScript 类型不是密封的或“精确的”,请参阅 extend 并仅指定已知属性? 了解更多信息):
const z = { a: "xyz", b: "" };
y = z; // okay
这意味着假设
a
属性是 undefined
或 string
确实不安全。这会导致可能的运行时错误:
x = y; // still okay, oops!
x.a?.toFixed(); // RUNTIME ERROR!
不过,这种情况在实践中往往不会经常发生,安全的替代方案将是“非常烦人”,因为常见的情况是大多数未知属性实际上都丢失了。这是人们一直写的东西:
const w = { b: "" };
x = w; // okay
function acceptX(x: { a?: number, b: string }) { }
acceptX(w); // okay
TypeScript 所知道的关于
w
的信息就是类型是
{b: string}
。它不记得初始化对象文字实际上缺少 a
属性。如果 TypeScript 每次都抱怨它不知道是否将未知属性分配给可选属性,那么上面的代码就会出错,这会让人们发疯。所以这是允许的。
这让我们interface AB {
a?: string;
c?: string;
b?: string;
}
interface CD {
a?: string;
c?: string;
d?: string;
}
这些类型是可以相互分配的,因为它们唯一不同的地方是一个类型具有可选属性而另一个缺少属性。它们都具有可选的
a
和
c
类型的 string
属性。但是 AB
在 b
中缺少可选的 CD
属性,并且 CD
在 d
中缺少可选的 AB
属性。这些差异都不被视为可分配性问题。事实上,只要您不开始使用 {b: 123, d: 456}
进行操作,其中相关属性存在但不存在-string
,您就不会看到此类交叉分配的问题。所以它的行为符合预期。
Playground 代码链接