TypeScript中的这些语句(接口与类型)有什么区别?

interface X {
    a: number
    b: string
}

type X = {
    a: number
    b: string
};

当前回答

2021 3月更新:更新的TypeScript手册(也在nju-clc中提到下面的答案)有一节“接口与类型别名”,解释了区别。


原始答案(2016)

根据(现已存档)TypeScript语言规范:

与总是引入命名对象类型的接口声明不同,类型别名声明可以引入任何类型的名称,包括基元类型、联合类型和交集类型。

该规范还提到:

接口类型与对象类型的类型别名有许多相似之处但是由于接口类型提供了更多的功能通常优选键入别名。例如,接口类型接口点{x: 数量;y: 数量;}可以写成类型别名类型点={x: 数量;y: 数量;};但是,这样做意味着失去以下功能:接口可以在extends或implements子句中命名,但对象类型文本的类型别名不能在TS 2.7之后为true。接口可以有多个合并声明,但对象类型文本的类型别名不能。

其他回答

类型示例:

//为对象创建树结构。由于缺少交集(&),您无法对接口执行相同的操作

type Tree<T> = T & { parent: Tree<T> };

//键入以限制变量仅分配几个值。接口没有联合(|)

type Choise = "A" | "B" | "C";

//由于类型,您可以通过条件机制声明NonNullable类型。

type NonNullable<T> = T extends null | undefined ? never : T;

界面示例:

//您可以使用OOP接口,并使用“implements”定义对象/类骨架

interface IUser {
    user: string;
    password: string;
    login: (user: string, password: string) => boolean;
}

class User implements IUser {
    user = "user1"
    password = "password1"

    login(user: string, password: string) {
        return (user == user && password == password)
    }
}

//可以使用其他接口扩展接口

    interface IMyObject {
        label: string,
    }

    interface IMyObjectWithSize extends IMyObject{
        size?: number
    }

何时使用类型?


通用转换

将多个类型转换为单个泛型类型时,请使用该类型。

例子:

type Nullable<T> = T | null | undefined
type NonNull<T> = T extends (null | undefined) ? never : T

类型别名

我们可以使用该类型为长类型或复杂类型创建别名,这些类型既难以阅读,又不方便反复键入。

例子:

type Primitive = number | string | boolean | null | undefined

创建这样的别名使代码更加简洁易读。


类型捕获

当类型未知时,使用该类型捕获对象的类型。

例子:

const orange = { color: "Orange", vitamin: "C"}
type Fruit = typeof orange
let apple: Fruit

在这里,我们得到了未知类型的橙子,称其为水果,然后使用水果创建一个新型的安全对象苹果。


何时使用界面?


多态性

接口是实现数据形状的契约。使用该接口明确表示它将被实现并用作关于如何使用对象的约定。

例子:

interface Bird {
    size: number
    fly(): void
    sleep(): void
}

class Hummingbird implements Bird { ... }
class Bellbird implements Bird { ... }

尽管您可以使用类型来实现这一点,但Typescript更多地被视为一种面向对象的语言,并且接口在面向对象语言中具有特殊的地位。当您在团队环境中工作或为开源社区做出贡献时,使用界面更容易阅读代码。对于来自其他面向对象语言的新程序员来说也很容易。

官方的Typescript文档还说:

…我们建议尽可能在类型别名上使用接口。

这也表明该类型更适合于创建类型别名,而不是创建类型本身。


声明合并

您可以使用接口的声明合并功能向已声明的接口添加新的财产和方法。这对于第三方库的环境类型声明很有用。当第三方库缺少某些声明时,可以使用相同的名称再次声明接口,并添加新的财产和方法。

例子:

我们可以扩展上述Bird接口以包含新的声明。

interface Bird {
    color: string
    eat(): void
}

就是这样!记住什么时候使用比迷失在两者之间的细微差别中更容易。

想加上我的2美分;

我曾经是“界面爱好者”(除了联合、交叉等以外,我更喜欢界面而不是类型)。。。直到我开始使用类型“any key value object”,即Record<string,unknown>

如果键入“任意键值对象”:

function foo(data: Record<string, unknown>): void {
  for (const [key, value] of Object.entries(data)) {
    // whatever
  }
}

如果你使用接口,你可能会走到死胡同

interface IGoo {
  iam: string;
}

function getGoo(): IGoo {
  return { iam: 'an interface' };
}

const goo = getGoo(); 

foo(goo); // ERROR
// Argument of type 'IGoo' is not assignable to parameter of type 
// 'Record<string, unknown>'.
//  Index signature for type 'string' is missing in type 
// 'IGoo'.ts(2345)

虽然打字就像一个符咒:

type Hoo = {
  iam: string;
};

function getHoo(): Hoo {
  return { iam: 'a type' }; 
}

const hoo = getHoo(); 

foo(hoo); // works

这个特定的用例——IMO——产生了不同。

2022年更新-

类型别名和接口非常相似,在许多情况下可以自由选择。界面的几乎所有功能在类型中可用,关键区别是类型不能重新打开以添加新的财产,而不是始终可延伸。

https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-在类型别名和接口之间

接口与类型

接口和类型用于描述对象和原语的类型。接口和类型通常可以互换使用,并且通常提供类似的功能。通常由程序员选择自己的偏好。

然而,接口只能描述创建这些对象的对象和类。因此,必须使用类型来描述字符串和数字等原语。

下面是接口和类型之间的两个区别的示例:

// 1. Declaration merging (interface only)

// This is an extern dependency which we import an object of
interface externDependency { x: number, y: number; }
// When we import it, we might want to extend the interface, e.g. z:number
// We can use declaration merging to define the interface multiple times
// The declarations will be merged and become a single interface
interface externDependency { z: number; }
const dependency: externDependency = {x:1, y:2, z:3}

// 2. union types with primitives (type only)

type foo = {x:number}
type bar = { y: number }
type baz = string | boolean;

type foobarbaz = foo | bar | baz; // either foo, bar, or baz type

// instances of type foobarbaz can be objects (foo, bar) or primitives (baz)
const instance1: foobarbaz = {y:1} 
const instance2: foobarbaz = {x:1} 
const instance3: foobarbaz = true