07 接口与类型别名

668 阅读4分钟

前言: TypeScript中的基础类型、函数类型和类类型, 在JavaScript都有对应的与法. 但是接口类型类型别名却是TypeScript独有的, 使其具备了JavaScript缺少的描述复杂数据结构的能力(如果要做到这点, JavaScript只能通过文档和大量的注释).

interface 接口类型

通过接口类型, 我么你可以i定义模块内, 跨模块, 跨项目代码的通信规则.

TypeScript对对象的类型检测遵循“鸭子类型”(duck typing)或者“结构化类型”(structural subtyping)的准则,即只要两个对象的结构一致,属性和方法一致,则它们就是一致的。

function Study(language: {name: string, age:() => number}) {
    console.log(`Program language ${language.name} created ${language.age()} years ago!`)
}

Study({
    name: 'TypeScript',
    age: () => new Date().getFullYear() - 2012
})

需要注意的是: 直接传入多余参数, 会报ts2345错误, 而先声明变量(有多余参数),在调用方法的话,则不会报错。这并非是疏忽或bug, 这种情况,我们称之为对象字面量的freshness。 具体如下:

// ts(2345) 实参(Argument)与形参(Parameter)类型不兼容,不存在属性id
Study({
    id: 2,
    name: 'Typescript',
    age: () => new Date().getFullYear() - 2012
})

ts类型声明与js结构容易混淆

// js, aliasName 类型别名
function StudyJavascript({name: aliasName}) {}

//ts 类型声明
function StudyTypeScript(language: {name: string}) {}

可缺省属性

使用属性名? 表示属性可以缺省。例子:

interface OptionalInterface {
    name: string,
    age?: () => number
}

当属性使用 ? 修饰后,属性就变为指定类型与undefined类型组成联合类型

(() => number) | undefined;

既然值可能是undefined,如果我们需要对对象的属性或方法进行操作,就可以使用类型守卫或Optional Chain.

if (typeof OptionalTypeScript.age === 'function') {
    OptionalTypeScript.age()
}

OptionalTypeScriot.age?.()

只读属性

使用readonly修饰符修饰的属性, 可读不可写。需要注意的是, 在转移为JavaScript后, readonly修饰符会被抹除(并不能阻止对象的篡改)。

定义函数类型

仅定义函数类型,不包含函数的实现。

索引签名

可以使用[索引名:类型]的格式约束索引的类型。

应用: React中的Props & State, HTMLElement的props

interface LanguageRankInterface {
 [rank: number]: string  // 键值之能是number类型
}

let languageRankMap: LanguageRankInterface = {
 1: 'Typescript',
 2: 'JavaScript',
 worngIndex: '2021' // ts2322
}

interface LanguageYearInterface {
 [name: string]: number
}

let languageMap: LanguageYearInterface {
    Typescript: 2012,
    javascript: 1995,
    1: 1970 // ok
}

注意: 数字作为对象索引时,它的类型既可以与数字兼容,也可以与字符串兼容,这与JavaScript的行为一致。因此, 使用0 或 ‘0’等价

使用readonly 直接索引签名

interface LanguageRankInterface {
    readonly [rank: string]: string;
    age: number; // ts 2411, age的属性string不能赋值给字符串索引类型
}

注意: 虽然属性可以与索引签名进行混用,但是属性的类型必须是对应的数字索引或字符串索引的类型的子类型,否则会出现错误提示。

不能约束数字索引属性与字符串索引属性拥有截然不同的类型。

interface LanguageRankInterface {
    [rank: number]: string, // ts 2413, 数字索引类型string类型不能赋值给字符串索引类型number
    [prop: string]: number;
}

如果需要使用age是nunber类型,其他属性类型时string的对象数据结构,要你管管如何定义呢?

{
    age: 1,
    otherProperty: 'string'
}

Tips: 使用联合类型属性合并时的类型缩减特性 传送门

继承与实现

接口可以继承接口,使用extends关键字,使用类实现接口,用 implements关键字表示

继承示例

{
    interface DynamicLanguage extends ProgramLanguage {
        rank: number
    }
    
    interface TypeScriptLanguage extends DynamicLanuage, TypeSafeLanuage {
        name: 'TypeScript'
    }
    
    // 仅能用兼容的类型来覆盖继承的属性
    interface WrongTypeLanuage extends ProgramLanuage {
        name: number // ts6196
    }
}

实现示例

{
    class LanuageClass implements Programlanguage {
        name: string = '';
        age = 8
    }
}

Type 类型别名

接口类型的一个作用是将内联类型抽离出来, 从而实现类型可服用。其实,我们也可以使用类型别名接收抽离出来的内联类型实现服用。使用type关键字

{
    type LanuageType = {
        name: string;
        age: () => number
    }
}

解决接口无法覆盖的场景

针对接口类型无法覆盖的场景,比如组合类型,交叉类型,只能用类型别名来接收。

// 类型联合
type MixedType = string | number;

// 交叉
type InrersectionType = { id: number; name: string } & { age: number }

// 提取接口属性类型
type AgeType = IntersectionType['age']

interface和Type的区别

适用接口类型标注的地方大都可以使用类型别名进行替代。 某些场景下还是有所区别。

接口具有特点

  1. 可选属性
  2. 只读属性
  3. 多余属性检查
  4. 函数类型
  5. 索引类型
  6. 类类型(继承,实现)
interface BaseInterface {
     readonly id: number;
}
interface SampleInterface exends BaseInterface {
    name?: string;
   //  (source:string, substing: strign): boolean;
    age: () => number;
    [index: string]: number | string | boolean  
}

class demo implements SampleInterface {
 // ....
}

区别

  1. 接口重复定义,它的属性会叠加。 该他行使得我们可以方便地对全局变量,第三方库的类型做扩展。重复定义类型, 会报错(ts2300)。
  2. 接口可以被继承和实现, 类型不行(但可以通过联合,交叉来扩展类型)
  3. 接口有索引签名,可以用来约束类(不用指定属性名称),
{
    interface Language {
        id: number
    }
    interface Language {
        name: string
    }
    
    let lang: Language {
        id: 1;
        name: 'name'
    }
}