20

!当断言一个字段在类中肯定被初始化时, (感叹号,明确赋值断言)和declare修饰符有什么区别?

以下代码是严格模式下的错误,因为 TS 不确定该字段是否已初始化。

class Person {
    name: string; // Error: Property 'name' has no initializer and is not definitely assigned in the constructor.
}

我见过两种处理方法:

  1. 确定赋值断言:
    class Person {
        name!: string;
    }
    
  2. 环境声明:
    class Person {
        declare name: string;
    }
    

我看不出这两种技术之间的区别。它们都解决了错误,它们都不发出代码,并且它们都不允许初始化程序。环境声明(在 v3.7 中发布)是否简单地过时明确的分配(在 v2.7 中发布)?应该declare尽可能使用而不是使用!

4

1 回答 1

46

在使用类型系统时,Declare 主要用于模拟值。在生产代码中,它很少使用。


declare name: string;

这对编译器说:

“有一个叫做nametype的属性string。我不应该向你证明它name确实存在,但我还是想使用它。”

declare关键字通常用于类型定义文件,这些文件为 Typescript 无法从中获取类型信息的文件(例如纯 JS 文件)提供类型。因此,如果我正在阅读您的代码,我会假设这name是从某个 JS 文件中的某个地方修补了猴子,而您在这里注意到了这一点。

我会不正确。


name!: string;

这对编译器说:

“有一个名为name类型的属性string | undefined。它以 值开头undefined。但是每次我获取或设置该属性时,我都想将其视为 type string。”

使用这种形式,任何阅读最初未定义的代码的人都清楚name,但无论如何都被视为字符串。这意味着它必须在这个文件的某个地方设置,只是可能不在构造函数中。

从你所说的来看,我在这些假设中是正确的。


在实践中,结果几乎相同。在这两种情况下,您都有一个不需要实际初始化的字符串属性。但是,我认为这对于name!: string实际发生的事情要清楚得多。

此外,declare 从不发出代码。它使某些东西只存在于类型系统中。(感谢您提到这个@NoelDeMartin)

class Foo {
    bar!: string;
    declare baz: string;
}

let bar: string
declare let baz: string

编译为:

class Foo {
    constructor() {
        Object.defineProperty(this, "bar", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}
let bar;

请注意,baz输出中完全没有。


最后,我不得不提一下,你可能应该重构你的代码,以便你可以在构造函数中分配属性。这两种方法都不是类型安全的,因为您可能会将未初始化的值视为 a string,如果发生这种情况可能会导致崩溃。

于 2021-05-02T08:15:25.630 回答