类型和接口之间的区别

摘抄自《TypeScript编程》Boris Cherny著 安道译

1. 类型和接口的共同点

与类型别名相似,接口是一种命名类型的方式,这样就不用在行内定义了。类型别名和接口算是同一概念的两种语法(就像函数表达式和函数声明之间的关系),不过二者之间还是有一些细微差别。先看二者的共同点。以下述类型别名为例:

在使用Sushi类型别名的地方都能使用Sushi接口。两个声明都定义结构,而且二者可以相互赋值(其实,二者完全一样)。

// 类型
type Sushi = {
    calories: number,
    salty: boolean,
    tasty: boolean
}

// 接口
interface Sushi {
    calories: number,
    salty: boolean,
    tasty: boolean
}

把类型组合在一起时,更为有趣。下面为Sushi之外的另一个食物建模:

type Sushi = {
    calories: number,
    salty: boolean,
    tasty: boolean
}

type Cake = {
    calories: number,
    sweet: boolean,
    tasty: boolean
}

// 类型组合
type Food = {
    calories: number,
    tasty: boolean
}
type Sushi = Food & {
    salty: boolean
}
type Cake = Food & {
    sweet: boolean
}

// 接口定义
interface Food {
    calories: number,
    tasty: boolean
}
interface Sushi extends Food {
    salty: boolean
}
interface Cake extends Food {
    sweet: boolean
}

2. 类型和接口的细微差别

1)类型别名更为通用,右边可以是任何类型,包括类型表达式(类型,外加&或|等类型运算符);而在接口声明中,右边必须为结构。例如,下述类型别名不能使用接口重写:

type A = number;
type B = A | string;

2)扩展接口时,TypeScript将检查扩展的接口是否可赋值给被扩展的接口。而使用交集类型时则不会出现这种问题。如果把下例中的接口换成类型别名,把extends换成交集运算符(&),TypeScript将尽自己所能,把扩展和被扩展的类型组合在一起,最终的结果是重载bad的签名,而不会抛出编译时错误。建模对象类型的继承时,TypeScript对接口所做的可赋值性检查是捕获错误的有力工具。

interface A {
    good(x: number): string,
    bad(x: number): string
}

/**
 * Interface 'B' incorrectly extends interface 'A'.
    Types of property 'bad' are incompatible.
    Type '(x: string) => string' is not assignable to type '(x: number) => string'.
        Types of parameters 'x' and 'x' are incompatible.
        Type 'number' is not assignable to type 'string'.ts(2430)
 */
interface B extends A {
    good(x: string | number): string,
    bad(x: string): string
}

3)第三个区别是,同一作用域中的多个同名接口将自动合并;同一作用域中的多个同名类型别名将导致编译时错误。这个特性称为声明合并。

posted @ 2023-05-26 15:02  黄燃  阅读(77)  评论(0编辑  收藏  举报