在TypeScript中,“declare class”和“interface”的区别是什么?

141
在 TypeScript 中,创建 .d.ts 源声明文件时,哪种方式更可取?为什么?
declare class Example {
    public Method(): void; 
}

或者

interface Example {
    Method(): void;
}

我能看出的区别是接口不能有静态方法,所以必须使用类。两者都不会产生任何JS输出,所以也许并不重要?

请查看http://stackoverflow.com/a/14323673/1704166。 - Ryan Cavanaugh
我不觉得这两者中的任何一个真正有助于描述你会如何使用其中之一,因为它们都可以在没有JS输出的情况下完成相同的事情。 - Chris
1
使用声明时,您必须确保类的实现在运行时存在,而使用接口则不需要。 - Hakim
4个回答

185

interface用于描述对象的形状,不会生成任何代码,仅是类型系统中的一个构件。对于类的代码生成,无论是否有implements子句,都不会有任何差异。

declare class用于描述一个已存在的类(通常是TypeScript类,但不总是),该类将在外部存在(例如,您有两个.ts文件编译为两个.js文件,并通过script标签包含在网页中)。如果您使用extendsclass继承(无论基类型是declare class还是常规class),编译器将生成所有代码来连接原型链和转发构造函数等。

如果您试图从应该是接口的declare class继承,那么您会遇到运行时错误,因为生成的代码将引用一个没有运行时表现的对象。

相反,如果您只是实现了应该是declare class的接口,那么您将不得不重新实现所有成员,并且不会利用来自潜在基类的任何代码重用,并且在运行时检查原型链的函数将拒绝将您的对象视为基类实例。

如果您有C++背景,可以粗略地将interface视为typedef,将declare class视为在本编译单元中严格缺少定义的构造函数的extern声明。

就消费方面而言(编写命令式代码,不添加新类型),interfacedeclare class 之间唯一的区别是你无法通过 new 关键字实例化一个接口。但是,如果你打算在一个新的类中 extends/implement 这些类型中的一个,那么你绝对必须正确地选择 interfacedeclare class 中的一个,只有其中一个才能起作用。

以下两条规则将为你服务:

  • 此类型的名称是否与构造函数名称(可以使用 new 调用的东西)对齐,并且在运行时实际存在(例如,Date 存在,但 JQueryStatic 不存在)?如果答案是否定的,则你肯定需要使用 interface
  • 我正在处理来自另一个 TypeScript 文件的已编译类或足够相似的东西吗?如果答案是肯定的,请使用 declare class

你实际上可以在TypeScript中新建一个接口。唯一的限制是继承。 - Oleg Mihailik
3
你不能在接口类型上调用 new 运算符。然而,接口可以有构造签名,这意味着你可以在接口类型的值上调用 new 运算符。这与 class 的工作方式非常不同,因为构造函数签名在类型名称本身上而不是在该类型的表达式上。 - Ryan Cavanaugh
如果您选择在接口中添加构造函数,它应该是接口的唯一成员,除了类的“静态(static)”部分。不要将构造函数的接口与构造的对象的接口结合在一起。如果这样做,类型系统会允许一些荒谬的情况,如: new (new x()),其中 x:Interface。 - Jeremy Bell

32

你可以实现这个接口:

class MyClass implements Example {
    Method() {

    }
}

declare class语法实际上是用于为不是用TypeScript编写的外部代码添加类型定义的 - 因此,其实现是“在别处”。


所以你是在建议声明类应该用来描述不是用TypeScript编写的代码?我会认为这样的情况,但在jquery.d.ts文件中被检查到的,JQueryStatic是一个由以下内容实现的接口: 声明变量$ : JQueryStatic 我本来以为应该是这样的: 声明类 $ { public static ... } - Chris
我能想到的唯一原因是,如果您不希望人们扩展类,则可以使用接口 - 使用接口意味着您必须提供整个实现。 - Fenton
有道理。也许那就是原因。 - Chris
好的,那么我正在尝试指向另一个JS库中的声明,因此我肯定需要使用declare。代码在一些库函数(类)上使用静态内容 - 到目前为止,我还没有看到在声明文件中表达静态属性或方法。哦,我应该使用命名空间还是模块? - jenson-button-event

15

通俗来讲,在.ts/d.ts文件中使用declare关键字告诉编译器我们期望在该环境中存在声明的关键字,即使它在当前文件中没有定义。这将允许我们在使用声明对象时具有类型安全性,因为Typescript编译器现在知道某些其他组件可能会提供该变量。


1
如果在当前文件中未定义,我们如何找到实现代码的位置? - avocado

9

declareinterface在TS中的区别:

declare:

用于告诉编译器有哪些全局变量已经存在,不需要编译器再去查找或报错。

interface:

用于定义一个对象的属性和方法的类型,可以被类、函数等实现或继承。

declare class Example {
    public Method(): void; 
}

在上述代码中,declare 声明了 TS 编译器在某个地方有一个名为 Example 的类。这并不意味着该类会自动包含进来。作为程序员,在声明时(使用 declare 关键字),需要确保该类可用。 接口:
interface Example {
    Method(): void;
}

接口是一个虚拟构造,仅存在于typescript中。typescript编译器使用它来进行类型检查。当代码编译为javascript时,整个结构将被剥离。typescript编译器使用接口来检查对象是否具有正确的结构。

例如,当我们有以下接口时:

interface test {
  foo: number,
  bar: string,
}

我们定义的具有该接口类型的对象需要完全匹配该接口:
// perfect match has all the properties with the right types, TS compiler will not complain.
  const obj1: test = {   
    foo: 5,
    bar: 'hey',
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接