打字稿:接口与类型

时间:2016-05-15 01:53:53

标签: typescript

这些语句(界面与类型)之间有什么区别?

interface X {
    a: number
    b: string
}

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

19 个答案:

答案 0 :(得分:388)

根据TypeScript Language Specification

  

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

规范接着提到:

  

接口类型与对象类型的别名类型有许多相似之处   文字,但由于界面类型提供了更多的功能   通常喜欢键入别名。例如,接口类型

interface Point {
    x: number;
    y: number;
}
     

可以写为类型别名

type Point = {
    x: number;
    y: number;
};
     

但是,这样做意味着丢失了以下功能:

     
      
  • 接口可以在extends或implements子句中命名,但是对象类型文字的类型别名不能不再为真,因为TS 2.7。
  •   
  • 接口可以有多个合并声明,但对象类型文字的类型别名不能。
  •   

答案 1 :(得分:227)

2019更新


当前答案和official documentation已过时。对于TypeScript的新手来说,没有示例就不清楚使用的术语。下面是最新的差异列表。

1。对象/功能

两者都可以用来描述对象的形状或功能签名。但是语法不同。

界面

interface Point {
  x: number;
  y: number;
}

interface SetPoint {
  (x: number, y: number): void;
}

键入别名

type Point = {
  x: number;
  y: number;
};

type SetPoint = (x: number, y: number) => void;

2。其他类型

与接口不同,类型别名也可以用于其他类型,例如基元,并集和元组。

// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

3。扩展

两者都可以扩展,但是语法也有所不同。此外,请注意,接口和类型别名不是互斥的。接口可以扩展类型别名,反之亦然。

界面扩展界面

interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }

类型别名扩展了类型别名

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

接口扩展了类型别名

type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

类型别名扩展了界面

interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

4。工具

一个类可以以完全相同的方式实现接口或类型别名。但是请注意,类和接口被视为静态蓝图。因此,他们不能实现/扩展名为联合类型的类型别名。

interface Point {
  x: number;
  y: number;
}

class SomePoint implements Point {
  x: 1;
  y: 2;
}

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x: 1;
  y: 2;
}

type PartialPoint = { x: number; } | { y: number; };

// FIXME: can not implement a union type
class SomePartialPoint implements PartialPoint {
  x: 1;
  y: 2;
}

5。声明合并

与类型别名不同,接口可以定义多次,并且将被视为单个接口(所有声明的成员都将被合并)。

// These two declarations become:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };

答案 2 :(得分:56)

2021 年相关

对于typescrpt 版本:4.3.4


TLDR;

我在下面描述的个人习惯是这样的:

<块引用>

总是喜欢 interface 而不是 type

何时使用 type

  • 在为原始类型(字符串、布尔值、数字、bigint、符号等)定义别名时使用 type
  • 在定义元组类型时使用 type
  • 在定义函数类型时使用 type
  • 在定义联合时使用 type
  • 尝试通过组合重载对象类型中的函数时使用 type
  • 在需要利用映射类型时使用 type

何时使用 interface

  • 对不需要使用 interface 的所有对象类型使用 type(见上文)
  • 如果您想利用声明合并的优势,请使用 interface

原始类型

typeinterface 之间最容易看到的区别是只有 type 可用于为原语设置别名:

type Nullish = null | undefined;
type Fruit = 'apple' | 'pear' | 'orange';
type Num = number | bigint;

这些示例都无法通过接口实现。

? 为原始值提供类型别名时,请使用 type 关键字。

元组类型

元组只能通过 type 关键字输入:

type row = [colOne: number, colTwo: string];

? 在为元组提供类型时使用 type 关键字。

函数类型

函数可以通过 typeinterface 关键字输入:

// via type
type Sum = (x: number, y: number) => number;

// via interface
interface Sum {
  (x: number, y: number): number;
}

由于两种方式都可以达到相同的效果,因此规则是在这些场景中使用 type,因为它更容易阅读(并且不那么冗长)。

? 在定义函数类型时使用 type

联合类型

联合类型只能通过 type 关键字实现:

type Fruit = 'apple' | 'pear' | 'orange';
type Vegetable = 'broccoli' | 'carrot' | 'lettuce';

// 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
type HealthyFoods = Fruit | Vegetable;

? 定义联合类型时,使用 type 关键字

对象类型

javascript 中的对象是键/值映射,“对象类型”是打字稿键入这些键/值映射的方式。在为对象提供类型时,interfacetype 都可以使用,因为原始问题已经清楚了。那么对于对象类型,您什么时候使用 typeinterface

交叉与继承

通过类型和组合,我可以做这样的事情:

interface NumLogger { 
    log: (val: number) => void;
}
type StrAndNumLogger = NumLogger & { 
  log: (val: string) => void;
}

const logger: StrAndNumLogger = {
  log: (val: string | number) => console.log(val)
}

logger.log(1)
logger.log('hi')

打字稿非常高兴。如果我尝试使用接口扩展它呢:


interface StrAndNumLogger extends NumLogger { 
    log: (val: string) => void; 
};

StrAndNumLogger 的声明给了我一个 error

Interface 'StrAndNumLogger' incorrectly extends interface 'NumLogger'

对于接口,子类型必须与超类型中声明的类型完全匹配,否则 TS 会抛出类似上面的错误。

? 在尝试重载对象类型中的函数时,最好使用 type 关键字。

声明合并

打字稿中接口与类型区分开来的关键方面是它们可以在声明后使用新功能进行扩展。当您想要扩展从节点模块导出的类型时,会出现此功能的常见用例。例如,@types/jest 导出在使用 jest 库时可以使用的类型。但是,jest 还允许使用新函数扩展主要的 jest 类型。例如,我可以添加这样的自定义测试:

jest.timedTest = async (testName, wrappedTest, timeout) =>
  test(
    testName,
    async () => {
      const start = Date.now();
      await wrappedTest(mockTrack);
      const end = Date.now();

      console.log(`elapsed time in ms: ${end - start}`);
    },
    timeout
  );

然后我可以这样使用它:

test.timedTest('this is my custom test', () => {
  expect(true).toBe(true);
});

现在,一旦测试完成,该测试所用的时间将打印到控制台。伟大的!只有一个问题 - typescript 不知道我添加了 timedTest 函数,所以它会在编辑器中抛出一个错误(代码可以正常运行,但 TS 会生气)。

为了解决这个问题,我需要告诉 TS 在 jest 已经可用的现有类型之上有一个新类型。为此,我可以这样做:

declare namespace jest {
  interface It {
    timedTest: (name: string, fn: (mockTrack: Mock) => any, timeout?: number) => void;
  }
}

由于接口的工作方式,此类型声明将与从 @types/jest 导出的类型声明合并。所以我不只是重新声明 jest.It;我使用新函数扩展了 jest.It,以便 TS 现在知道我的自定义测试函数。

type 关键字无法实现这种类型的事情。如果 @types/jesttype 关键字声明了它们的类型,我将无法用我自己的自定义类型扩展这些类型,因此没有什么好方法让 TS 满意我的新功能。这个 interface 关键字独有的过程称为 declaration merging

声明合并也可以像这样在本地进行:

interface Person {
  name: string;
}

interface Person {
  age: number;
}

// no error
const person: Person = {
  name: 'Mark',
  age: 25
};

如果我对 type 关键字执行上述完全相同的操作,则会收到错误消息,因为类型无法重新声明/合并。在现实世界中,javascript 对象很像这个 interface 示例;它们可以在运行时使用新字段动态更新。

? 因为接口声明可以合并,所以接口比类型更准确地表示 javascript 对象的动态性质,因此它们应该是首选。

映射对象类型

使用 type 关键字,我可以像这样利用 mapped types

type Fruit = 'apple' | 'orange' | 'banana';

type FruitCount = {
  [key in Fruit]: number;
}

const fruits: FruitCount = {
  apple: 2,
  orange: 3,
  banana: 4
};

这不能用接口来完成:

type Fruit = 'apple' | 'orange' | 'banana';

// ERROR: 
interface FruitCount {
  [key in Fruit]: number;
}

? 当需要利用映射类型时,使用 type 关键字

性能

大多数时候,对象类型的简单类型别名的作用与接口非常相似。

interface Foo { prop: string }

type Bar = { prop: string };

但是,一旦您需要组合两个或多个类型,您就可以选择使用接口扩展这些类型,或者将它们交叉在一个类型别名中,这就是差异开始变得重要的时候。

接口创建了一个单一的平面对象类型来检测属性冲突,这通常很重要!另一方面,交叉点只是递归地合并属性,在某些情况下永远不会产生。界面也始终显示得更好,而交叉点的类型别名不能在其他交叉点的一部分中显示。接口之间的类型关系也被缓存,而不是作为一个整体的交集类型。最后一个值得注意的区别是,在检查目标交叉点类型时,在检查“有效”/“扁平”类型之前,先检查每个成分。

出于这个原因,建议使用接口/扩展扩展类型而不是创建交叉类型。

更多关于typescript wiki

答案 3 :(得分:36)

从TypeScript 3.2(2018年11月)开始,以下是正确的:

enter image description here

答案 4 :(得分:23)

https://www.typescriptlang.org/docs/handbook/advanced-types.html

  

一个区别是接口创建了一个在任何地方都使用的新名称。类型别名不会创建新名称 - 例如,错误消息将不使用别名。

答案 5 :(得分:21)


何时使用type


通用转换

将多种类型转换为单个通用类型时,请使用type

示例:

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

类型别名

我们可以使用type为难以理解的长或复杂类型创建别名,并且不方便一次又一次地键入。

示例:

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

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


捕获类型

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

示例:

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

在这里,我们得到orange的未知类型,将其称为Fruit,然后使用Fruit创建一个新的类型安全对象apple


何时使用interface


多态

interface是用于实现数据形状的协定。使用该接口可以清楚地表明该对象旨在实现并用作有关如何使用该对象的合同。

示例:

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

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

尽管您可以使用type来实现此目的,但Typescript被更多地视为一种面向对象的语言,而interface在面向对象的语言中占有特殊的位置。在团队环境中工作或为开源社区做出贡献时,使用interface读取代码会更容易。来自其他面向对象语言的新程序员也很容易。

官方打字稿documentation也说:

......我们建议尽可能在interface别名上使用type

这还表明type比创建类型本身更适合用于创建类型别名。


声明合并

您可以使用interface的声明合并功能为已声明的interface添加新的属性和方法。这对于第三方库的环境类型声明很有用。如果缺少第三方库的某些声明,则可以再次使用相同的名称声明接口,并添加新的属性和方法。

示例:

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

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

就是这样!记住何时使用什么功能比迷失两者之间的细微差异要容易得多。

答案 6 :(得分:6)

其他答案都很棒! Type可以做但Interface做不到的其他事情

您可以在类型中使用并集

type Name = string | { FullName: string };

const myName = "Jon"; // works fine

const myFullName: Name = {
  FullName: "Jon Doe", //also works fine
};

遍历类型的联合属性

type Keys = "firstName" | "lastName";

type Name = {
  [key in Keys]: string;
};

const myName: Name = {
  firstName: "jon",
  lastName: "doe",
};

类型的交集(但是,在与extends的接口中也支持)

type Name = {
  firstName: string;
  lastName: string;
};

type Address = {
  city: string;
};

const person: Name & Address = {
  firstName: "jon",
  lastName: "doe",
  city: "scranton",
};

type相比,interface的引入较晚,并且根据最新版的TS type几乎可以完成interface可以做的所有事情!


* Declaration merging除外(个人观点:最好不提供类型上的支持,因为它可能导致代码不一致

答案 7 :(得分:1)

接口与类型

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

但是,接口只能描述对象和创建这些对象的类。因此,必须使用类型来描述诸如字符串和数字之类的基元。

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

// 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 

答案 8 :(得分:1)

这是另一个区别。如果您能解释这种情况的原因,我会...给您买啤酒。

netstat

除非我有意实现某种OOP设计模式,或者需要如上所述进行合并(除非我拥有一个非常的充分理由)。

答案 9 :(得分:1)

在打字稿中,建议使用“接口”而不是“类型”。

  • “type”用于创建类型别名。

     type Data=string
    

然后,您可以使用“数据”代替字符串

const name:string="Yilmaz"
const name:Data="Yilmaz"

别名非常有用,尤其是在处理泛型类型时。

你不能用“接口”来做到这一点。

  • 您可以合并接口,但不能合并类型。

    界面人{ 名称:字符串; }

      interface Person {
        age: number;
      }
    
      const me: Person = {
        name: "Yilmaz Bingol",
        age: 30
      };
    
  • 函数式编程用户使用“类型”,面向对象编程用户选择“接口”

  • 您不能在接口上拥有计算或计算属性,而只能在类型中拥有。

    type Fullname = "name" | “姓氏”

      type  Person= {
         [key in Keys]: string
      }
    
      const me: Person = {
         firstname: "Yilmaz",
         lastname: "Bingol"
      }
    

答案 10 :(得分:0)

除了已经提供的出色答案外,在扩展类型与接口之间还存在明显的差异。我最近遇到了几种界面无法完成工作的情况:

  1. Cannot extend a union type using an interface
  2. Cannot extend generic interface

答案 11 :(得分:0)

文档已说明

  
      
  • 一个区别是接口创建了一个新名称,该名称随处可见。类型别名不会创建新名称-例如,错误消息不会使用别名。在旧版本的TypeScript中,不能从中扩展或实现类型别名(也不能扩展/实现其他类型)。从2.7版开始,可以通过创建新的交集类型来扩展类型别名
  •   
  • 另一方面,如果您无法使用界面表达某种形状,而需要使用并集或元组类型,则通常使用类型别名。
  •   

Interfaces vs. Type Aliases

答案 12 :(得分:0)

好吧,“ typescriptlang”似乎建议尽可能在类型上使用接口。 @typescriptlang Interface vs Type Alias

答案 13 :(得分:0)

索引编制也有所不同。

interface MyInterface {
  foobar: string;
}

type MyType = {
  foobar: string;
}

const exampleInterface: MyInterface = { foobar: 'hello world' };
const exampleType: MyType = { foobar: 'hello world' };

let record: Record<string, string> = {};

record = exampleType;      // Compiles
record = exampleInterface; // Index signature is missing

因此,如果您要为对象建立索引,请考虑以下示例

看看这个question

答案 14 :(得分:0)

在编译速度方面,组合接口的性能优于类型交集:

[...]接口创建一个检测属性冲突的单一平面对象类型。这与交集类型相反,交集类型在检查有效类型之前先检查每个成分。与交叉点类型相反,接口之间的类型关系也被缓存。

来源:https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections

答案 15 :(得分:0)

Typescript手册给出了答案:关键区别在于,无法重新打开类型以添加​​新属性,而接口始终是可扩展的。

链接:https://www.typescriptlang.org/docs/handbook/advanced-types.html#interfaces-vs-type-aliases

答案 16 :(得分:0)

类型示例:

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

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

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

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

//由于类型,您可以借助条件机制来声明NonNullable类型。

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

接口示例:

//您可以将接口用于OOP并使用“实现”来定义对象/类的骨架

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
    }

答案 17 :(得分:0)

documentation 中指出的主要区别在于,Interface 可以重新打开以添加新属性,而 Type alias 不能重新打开以添加新属性,例如:

没关系

interface x {
  name: string
}

interface x {
  age: number
}

这会抛出错误 Duplicate identifier y

type y = {
  name: string
}

type y = {
  age: number
}

除此之外,接口和类型别名是相似的。

答案 18 :(得分:0)

它们在语义上是不同的。

接口是 TS 类型系统中的常规语法元素。它是 TS 语法的原生部分。

而类型别名是一种语法糖。这是一种元编程。