TypeScript(二):type & interface

97 阅读4分钟

typeinterface放在一起,是因为它们的某些行为很像,而区别又是一些完全不相关的特殊能力。

在进入本节整体之前也有一个概念要明确一下:

编程语言的子类型分为两种:名义子类型和结构子类型。名义子类型就是指,例如 Java 中类的继承,子类就是父类的子类型,而要建立父子类的关系只有一个办法就是extends(接口的implements也算),只有用了extends才会出现父子类型,也就是只能用extends才能让他们成为名义上的父子类型。结构子类型就是结构相同即可,而 TypeScript 就是结构子类型,即在 TypeScript 中

type Foo = {
  a: string
}

type Bar = {
  a: string
}

是一样的类型,不需要extends(尽管extends也可以创造子类型,但本质是结构相似)。所以

type Foo = {
  a: string
}
type Bar = {
  a: string,
  b: number
}

BarFoo的子类型。

相似的地方

对象自变量式的结构定义

interface Foo {
    a: string
}

type Foo {
    a: string
}

都是定义了一个有a属性的对象结构。

函数类型

函数类型其实由两个部分构成,参数类型和返回值类型。

interface Foo {
    (a: string): string
}

type Foo = (a: string) => string

表示同一类型的函数,关于函数类型会在下一篇文章做详细介绍。

混合类型

由于JavaScript具有动态和灵活的性质,有时可能会遇到一个对象,该对象可以作为上述某些类型的组合使用,就是一个既具有函数特性又具有对象特性的类型,它具有一些属性。如下:

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = (function (start: number) { }) as Counter;
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

当然其中的

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

也可以使用type实现 :

type Counter = {
    (start: number): string;
    interval: number;
    reset(): void;
}

关于|&操作符的行为完全相同

type Foo = {
    a: string
}
type Bar = {
    b: number
}
type Baz = Foo & Bar

interface Foo {
    a: string
}
interface Bar {
    b: number
}
type Baz = Foo & Bar

的类型完全相同,同样|操作符也是。

在我看来,&|操作符都会创建一个新的类型,而且是相关类型父子类型链上的类型。它们区别是他们的产物是在父子类型链上的不同角色。如上所示,第一段代码中,对FooBar使用了&操作符,创建的新的类型Baz,结果是BazFooBar的子类型,就是说,Bar类型的变量何以赋值给FooBar类型的变量。在第二段代码中,对FooBar使用了|操作符,创建的新的类型Baz,结果是BazFooBar的父类型,即,FooBar类型的变量可以赋值给Bar

可选属性

interface Foo {
    a?: string
}

type Foo = {
    a?: string
}

都表示a可有可无。

可选属性需要注意的地方是a?: string并不等于a: string | undefined,如果想知道为什么,可以看这个 issue 32695

索引类型

interface Foo {
    [x: string]: number
    [x: number]: string
}

type Foo = {
    [x: string]: number
    [x: number]: string
}

都表示索引为string的属性的类型都为number,索引为number的属性的类型都为string

用于类implements

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date = new Date();
    constructor(h: number, m: number) { }
}

在 TypeScript 中,implements操作只是为class提供一种类型约束,没有其他作用,所以,主要是符合class结构的类型都是可以被implements的。所以除了interface之外,type也可以进行implements。但因为 JavaScript 中引入class是照着 OOP 来做的,所以在需要implements时,使用interface有更强的语意性。

不同的地方

type

(1) 类型别称

很简单,就是为已经存在的类型创建另一个名字,代表完全相同的意义。例如:

type ObjectAlias = object

虽然说这个特性是type独有的,但当原类型不是原始类型时,即原类型不是numberstringbooleanobjectsymbolnullundefinedvoidneverunknownany时,interface可以使用以下方式实现类似的功能:

interface Foo {
  a: string
}
interface FooAlias extends Foo {}

interface

(1) 扩展接口(extends interface)

interface Foo {
  a: string
}
interface Bar extends Foo {
  b: number
}

这里使用&可以实现类似的效果,如下:

interface Foo {
  a: string
}
type Bar = Foo & {
  b: number
}

(2) 扩展类(extends class)

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

作者:M.TQ 链接:zhuanlan.zhihu.com/p/92906055\

在类声明时,其实也同时声明了一个描述该类结构和类型的接口,所以这里的扩展操作与上面(1)中的相似。

本文结束。