首页 > 解决方案 > 这是 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 的类型系统在很大程度上是结构性的,而不是名义上的。所以重要的是类型的形状结构,而不是它的名称声明。因此,TypeScript 编译器可以决定该类型A(或“可分配给”或“扩展”)的子类型,无论您是否在. 仅当 的表观性质在 中具有兼容性质时才重要:BABA BABBA

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 会自动将一些原语包装在包装器对象中。所以类型是 的子类型:numberstringboolean{length: number}string

interface L { length: number };
const l: L = "hello"; // okay

因为string值具有 type 的明显length属性number


在 TypeScript 中,class声明也通常在结构上进行处理(尽管在某些情况下会发生类似名义的类型,例如用于instanceof区分两个类添加privateprotected成员时)。因此,如果您有一个空类:

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, 因为如果您像对象一样对它们进行索引,则会引发运行时错误nullundefined


这就是它发生的原因。通常,您希望防止此类行为,因此最好避免使用空类和空对象类型,即使在示例代码中也是如此。(有些过时的)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不是声明

Playground 代码链接


推荐阅读