typescript - 这是 TypeScript 错误吗?
问题描述
我不确定为什么下面的代码片段有效......
class GroupLeader { /* snip */ };
function foo(leader: GroupLeader): void { /* snip: do stuff */ }
const isLeader = false;
const groupLeader = isLeader && new GroupLeader();
foo(groupLeader);
在 REPL 中,我可以看到groupLeader
最终是一个boolean
类型,但是 TypeScript 编译器(版本 4.4.3)在调用foo(groupLeader)
.
为什么这行得通?
解决方案
这是 TypeScript 中的预期行为。
TypeScript 的类型系统在很大程度上是结构性的,而不是名义上的。所以重要的是类型的形状或结构,而不是它的名称或声明。因此,TypeScript 编译器可以决定该类型是A
(或“可分配给”或“扩展”)的子类型,无论您是否在. 仅当 的表观性质在 中具有兼容性质时才重要:B
A
B
A
B
A
B
B
A
interface A {
x: string;
y: number;
}
interface B {
x: string;
}
const a: A = { x: "", y: 0 };
const b: B = a; // okay, A extends B
最后一行不像在名义上键入的语言中那样是错误的。
请注意,“表观属性”意味着即使是像、和等一些原语也可以被认为在结构上与对象类型兼容,因为当您对它们进行索引时,JavaScript 会自动将一些原语包装在包装器对象中。所以类型是 的子类型:number
string
boolean
{length: number}
string
interface L { length: number };
const l: L = "hello"; // okay
因为string
值具有 type 的明显length
属性number
。
在 TypeScript 中,class
声明也通常在结构上进行处理(尽管在某些情况下会发生类似名义的类型,例如用于instanceof
区分两个类或添加private
或protected
成员时)。因此,如果您有一个空类:
class GroupLeader { }
此类在结构上与空对象类型相同,空对象类型{}
是没有成员的对象类型。因此,任何可以像对象一样被索引的值都将被视为可分配给该类:
function foo(leader: GroupLeader): void { /* snip: do stuff */ }
foo(true); // okay
foo(123); // okay
foo({}); // okay
foo(() => 3); // okay
foo(new Date()); // okay
foo(Symbol("oops")); // okay
foo(null); // error
foo(undefined); // error
只有null
并且undefined
不可分配给GroupLeader
, 因为如果您像对象一样对它们进行索引,则会引发运行时错误null
。undefined
这就是它发生的原因。通常,您希望防止此类行为,因此最好避免使用空类和空对象类型,即使在示例代码中也是如此。(有些过时的)TypeScript FAQ有很多关于这个的条目,比如为什么所有类型都可以分配给空接口?为什么这些空类表现得很奇怪?. 如果您希望编译器将两种类型视为不同的类型,则应确保它们具有不兼容的形状。
添加任何boolean
不共享的属性GroupLeader
都会改变事情:
class GroupLeader { unsnip = 0 };
function foo(leader: GroupLeader): void { /* snip: do stuff */ }
foo(true); // error
foo(123); // error
foo({}); // error
foo(() => 3); // error
foo(new Date()); // error
foo(Symbol("oops")); // error
foo(new GroupLeader()); // okay
当然,仍然有可能传入一些结构上兼容的东西:
foo({ unsnip: 123 }); // okay
如果需要,您可以尝试阻止这种情况(private
属性会这样做),但阻力最小的路径是只编写只关心结构兼容性的代码。无论foo()
实现是什么,它都应该只关心的结构而leader
不是声明。
推荐阅读
- spring-boot - 在控制器中处理可为空的 UUID - SpringBoot 和 Postgres
- dynamics-crm - 在 CRM 设计中,是否必须在请求更改地址时强制更新电话号码?
- r - 在一张图像中合并几个并排的图,直到某个给定的上限
- ansible - 如何阻止ansible跳到第二个任务,直到第一个任务成功执行?
- postgresql - 如何在 PostgreSQL 中切换用户?我的意思是作为 PostgreSQL 中的另一个现有用户登录
- python - MultiIndexed pandas 数据框认为输入是唯一的
- amazon-web-services - Scala:获取 URL 参数
- react-native - 如何在反应原生聊天应用程序中实现类似 Whatsapp 的蓝色刻度功能
- sql - 从另一个表中逐行计数,在sql中的两个表上保持一些条件
- tensorflow - tf.keras.backend.function 用于转换 tf.data.dataset 中的嵌入