likes
comments
collection
share

TypeScript小状况之interface与type的对比

作者站长头像
站长
· 阅读数 24

在日常使用typescript的过程中,很容易发现interfacetype在一些情况下都能使用,那究竟它们之间有什么区别?什么时候该用interface,什么时候该用type呢?

先说结论:

比较项interfacetype
写法跟class类似变量赋值
拓展已有类型使用extends关键字使用交集(intersections)的形式
可声明类型对象、函数所有类型皆可
重复声明合并字段报错
重命名类型只能以extends的形式重名对象或函数可以重命名任意类型

写法

interface

跟其他编程语言类似,interface本身是对class的抽象,所以写法上也和class一样。

interface IObj {
    a: string
    b: number
    c: boolean
}

type

type根据官方的定义,叫做类型别名(type aliases),直观的理解就是给一个已有类型创建别名。这个已有类型可以是另一个类型别名,也可以是类型的字面量。看到这里,可以发现这个就和JS的变量的定义很类似;所以,它的写法就和变量赋值是一样的。

type TObj = {
    a: string
    b: number
    c: boolean
}

拓展已有类型

interface

需要注意的是,拓展的子接口不能把父接口已有的字段重新声明为其他类型,否则TSLint会报错。

interface IObj {
    a: string
    b: number
    c: boolean
}
interface ISubObj extends IObj {
    d: string
}

// Interface 'ISubObj2' incorrectly extends interface 'IObj'. 
    // Types of property 'c' are incompatible. 
        // Type 'string' is not assignable to type 'boolean'.
interface ISubObj2 extends IObj {
    c: string
    d: string
}

type

同样需要注意的重新声明为其他类型的情况,在type的使用下,类型声明是不会报错,因为TS解析为这是一个never(交集为空),所以报错的是在变量赋值的时候。

type TObj = {
    a: string
    b: number
    c: boolean
}
// 正确的拓展写法
type TSubObj = TObj & {
    d: string
}

// 不正确的拓展写法
type TSubObj2 = TObj & {
    c: string
    d: string
}

const obj2: TSubObj2 = {
    // Type 'string' is not assignable to type 'never'.
    a: 'abc',
    // Type 'number' is not assignable to type 'never'.
    b: 1,
    // Type 'string' is not assignable to type 'never'.
    c: 'abc',
    // Type 'string' is not assignable to type 'never'.
    d: 'xyz'
}

可声明类型

interface

interface只能声明对象(数组)和函数

interface IObj {
    a: string
    b: number
    c: boolean
}
interface IFunc {
    (a: number): boolean
}
interface IArr {
    [k: number]: string
}

const obj: IObj = {
    a: 'abc',
    b: 1,
    c: true,
}
const fn: IFunc = a => {
    return a >= 0
}
const arr: IArr = ['a', 'b', 'c']

type

type不仅能声明对象(数组)和函数,还可以对其他类型甚至原始类型重命名。

但要注意一点,当某个类型是联合类型(union type)时,是不能被类(class) 进行implements

type TMyStr = string
type TMyNum = number
type TSomeChar = 'a' | 'b' | 'c'
type TObj = {
    a: TMyStr
    b: number
    c: boolean
}
type TFunc = (a: number, b: number) => boolean
type TArr = {
    [k: number]: string
}

const str: TMyStr = 'hhhhh'
const num: TMyNum = 2333
const somechar: TSomeChar = 'a'
// Type '"x"' is not assignable to type 'TSomeChar'.
const somechar2: TSomeChar = 'x'
const obj: TObj = {
    a: 'abc',
    b: 1,
    c: true,
}
const fn: TFunc = (a, b) => {
    return a >= b
}
const arr: TArr = ['a', 'b', 'c']
type TObj = {
    a: TMyStr
    b: number
    c: boolean
}
type TObj2 = TObj | {
    d: boolean
}

// A class can only implement an object type or intersection of object types with statically known members.
class myClass implements TObj2 {
    a = 'xyz'
    b = 666
    c = false
}

另外,type还能使用typeof关键字把变量/字面量转化为类型,TS编译器会根据变量/字面量的值进行分析,判定这些值对应的类型。

const foo = {
    name: 'Jack',
    age: 18
}
type Tfoo = typeof foo

TypeScript小状况之interface与type的对比

重复声明

interface

重复声明同名interface,会把声明的字段合并,类似于上面拓展,已有的字段重新声明为其他类型也是会导致报错的。

interface IObj {
    a: string
    b: number
    c: boolean
}
interface IObj {
    d: string
}

const obj: IObj = {
    a: 'abc',
    b: 1,
    c: true,
    d: 'xyz'
}

type

type则是直接报错有重复的标识符。

// Duplicate identifier 'TObj'.
type TObj = {
    a: string
    b: number
    c: boolean
}
// Duplicate identifier 'TObj'.
type TObj= {
    d: string
}

重命名类型

interface

本身interface不支持重命名类型,但是可以使用extends的形式去重命名,然而在实际开发中我个人并不推荐这种做法。因为本质上没有拓展任何的字段,只是利用了语法上的特点,但会造成语义上和逻辑上的误解,降低代码可读性和理解的成本。

interface IObj {
    a: string
    b: number
    c: boolean
}
interface IObj2 extends IObj {
}

const obj: IObj2 = {
    a: 'abc',
    b: 1,
    c: true,
}

type

type正如它的定义所示,是类型别名,它不仅可以重命名自己声明的类型,还能对其他类型甚至原始类型重命名。

type TMyStr = string
type TObj = {
    a: TMyStr
    b: number
    c: boolean
}
type TObj2 = TObj

const obj: TObj2 = {
    a: 'abc',
    b: 1,
    c: true
}
type TMyStr = string
interface IObj {
    a: TMyStr
    b: number
    c: boolean
}
type TObj = IObj

const obj2: TObj = {
    a: 'abc',
    b: 1,
    c: true
}

总结

综上所述,interfacetype有不少相似之处,特别是在对象(数组)和函数的类型声明上,都可以相互替换使用。

interface更专注于对象(数组)和函数的抽象定义;type则更接近是typescript的一个包罗万象的兜底方案,而且能玩出很多花活和骚操作。具体可以看之前写的其他关于typescript的文章,例如我最近写的这两篇:TypeScript小状况之用数组元素组装数据类型TypeScript小状况之选且只选一个

我自己的简单宗旨是,在两者皆适用的场景下,尽可能优先使用interface,其他情况则用type