02高级类型

173 阅读20分钟

1. 高级类型

1.1 class

1.1.1 使用

TS全面支持 ES2015 中引入的 class 关键字,并为其添加了类型注解和其他语法(比如,可见性修饰符等)。

class 基本使用,如下:

class Person{}
const zs = new Person() // zs 的类型为 Person

解释:

  • 根据 TS 中的类型推论,可以知道 Person 类的实例对象 zs 的类型是 Person

  • TS 中的 class,不仅提供了 class 的语法功能,也作为一种类型存在

1.1.2 实例属性初始化

class Person {
    name: string
    age = 18
}

解释:

  • 声明成员 name,类型为 string(没有初始值)
  • 声明成员 age,并设置初始值,此时,可省略类型注解(TS 类型推论 为 number 类型)

1.1.3 构造函数

class Person {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}
const zs = new Person('zs', 18) // zs 的类型为 Person

解释:

  • 成员初始化(比如,age: number)后,才可以通过 this.age 来访问实例成员。

  • 需要为构造函数指定类型注解,否则会被隐式推断为 any

  • 构造函数不需要返回值类型

1.1.4 实例方法

class Point{
    x = 10
    y = 10
    scale(n:number):void{
        this.x *= n
        this.y *= n
    }
}

解释:方法的类型注解(参数和返回值)与函数用法相同

1.1.5 类继承

类继承的两种方式:

  • extends(继承父类)
  • implements(实现接口)

说明:JS 中只有 extends,而 implements 是 TS 提供的

1. extends

class Father {
    sayFather() {
        console.log('father')
    }
}

class Son extends Father{
    saySon(){
        console.log('son');
    }
}

const zs = new Son()

解释:

  • 通过 extends 关键字实现继承

  • 子类 Son 继承父类 Father,则 Son 的实例对象 zs 就同时具有了父类 Father 和 子类 Son 的所有属性和方法

2. implements

interface Father {
    name: string
    sayFather(): void
}

class Son implements Father {
    name: 'zs'
    sayFather(): number {
        return 1
    }
}

解释:

  • 通过 implements 关键字让 class 实现接口

  • Son 类实现接口 Father 意味着,Son 类中必须提供 Father接口中指定的所有方法和属性

1.1.6 类成员可见性

1. 概念

可以使用 TS 来控制 class 的方法或属性对于 class 外的代码是否可见。

可见性修饰符包括:

  • public(公有的)
  • protected(受保护的)
  • private(私有的)

2. public

public:表示公有的、公开的,公有成员可以被任何地方访问,默认可见性。

class Father {
    public name = 'father'
    public sayHello(){
        console.log('Hello') 
    }
}

解释:

  • 在类属性或方法前面添加 public 关键字,来修饰该属性或方法是共有的
  • 因为 public 是默认可见性,所以,可以直接省略

3. protected

protected:表示受保护的,仅对其声明所在类和子类中(非实例对象)可见。

class Father {
    name = 'father'
    protected sayHello() {
        console.log('Hello')
    }
}

class Son extends Father {
    age = 18
    sayHi() {
        console.log('Hi')
        this.sayHello()
    }
}

解释:

  • 在类属性或方法前面添加 protected 关键字,来修饰该属性或方法是受保护的

  • 在子类的方法内部可以通过 this 来访问父类中受保护的成员,但是,对实例不可见

4. private

private:表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的。

class Father {
    name = 'father'
    protected sayHello() {
        console.log('Hello')
        this.miss() // 只能在本类调用
    }
    private miss() {
        console.log('除了自己所在的类,其他方式均无法调用')
    }
}

解释:

  • 在类属性或方法前面添加 private 关键字,来修饰该属性或方法是私有的

  • 私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的

5. readonly

除了可见性修饰符之外,还有一个常见修饰符就是:readonly(只读修饰符)。

readonly:表示只读,用来防止在构造函数之外对属性进行赋值。

class Person{
    readonly age:number = 18
    constructor (age:number){
        this.age = age
    }
}

解释:

  • 使用 readonly 关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法

  • 注意:属性 age 后面的类型注解(比如,此处的 number)如果不加,则 age 的类型为 18 (字面量类型),这里说明 readonly 和 前面的 const 特性是一致的,单纯赋值不加类型,类型推断都是字面量

  • 接口或者 {} 表示的对象类型,也可以使用 readonly

1.2 类型兼容性

1.2.1 两种类型系统

两种类型系统:

  • Structural Type System(结构化类型系统)
  • Nominal Type System(标明类型系统)

TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型) ,类型检查关注的是值所具有的形状。也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。

class Point { x: number; y: number }
class Point2D { x: number; y: number }

const p: Point = new Point2D()

解释:

  • PointPoint2D 是两个名称不同的类
  • 变量 p 的类型被显示标注为 Point 类型,但是,它的值却是 Point2D 的实例,并且没有类型错误
  • 因为 TS 是结构化类型系统,只检查 PointPoint2D 的结构是否相同(相同,都具有 x 和 y 两个属性,属性类型也相同)
  • 如果在 Nominal Type System 中(比如,C#Java 等),它们是不同的类,类型无法兼容

1.2.2 对象类型的兼容

注意:在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确。更准确的说法:对于对象类型来说,y 的成员至少与 x 相同,则 x 兼容 y(成员多的可以赋值给少的

class Point2D { x: number; y: number }
class Point3D { x: number; y: number; z: number }

const p: Point3D = new Point2D() // 报错
const p: Point2D = new Point3D() // 正常

解释:Point3D 的成员至少与 Point2D 相同,则 Point2D 兼容 Point3D。所以,成员多的 Point3D 可以赋值给成员少的 Point2D

1.2.3 接口的兼容

接口之间的兼容性,类似于 class。并且,classinterface 之间也可以兼容。

interface 之间的兼容等同与 class

interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }

let p1: Point3D = {
    x: 1,
    y: 2,
    z: 3
}
let p2: Point2D = p1 // 正常

let p3: Point2D = {
    x: 1,
    y: 2
}

let p4:Point3D = p3 // 报错

interfaceclass 之间的兼容:

interface Point2D { x: number; y: number }
class Point3D { x: number; y: number; z: number }

let p1: Point2D = new Point3D()

1.2.4 函数的兼容

函数之间兼容性比较复杂,需要考虑:

  • 参数个数
  • 参数类型
  • 返回值类型

1. 参数个数

参数个数,参数多的兼容参数少的(或者说,参数少的可以赋值给多的)。函数中,如果参数传递的参数不够,那么后面的都默认为 undefined,但传递太多就直接出问题了。

let f1 = (a: number) => 0
let f2 = (a: number, b: number) => 0
f1 = f2 // 报错
f2 = f1 // 正常

解释:

  • 参数少的可以赋值给参数多的,所以,f1 可以赋值给 f2
  • 在 JS 中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了 TS 中函数类型之间的兼容性

2. 参数类型

参数类型,相同位置的参数类型要相同(原始类型)或兼容(对象类型)。

简单类型的参数:

let f1 = (a: number) => 0
let f2 = (a: number) => 0
f1 = f2 // 正常

let f3 = (a: number) => 0
let f4 = (a: string) => 0
f3 = f4 // 报错
f4 = f3 // 报错

解释:

  • 相同位置函数参数类型相同,是可以兼容的
  • 相同位置函数参数类型不同,不能兼容
  • 这里的参数位置指两边相同的位置都有参数

复杂类型的参数:

interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
let f2 = (p: Point2D) => 0 
let f3 = (p: Point3D) => 0 
f2 = f3 // 报错
f3 = f2 // 正常

解释:

  • 注意,此处与前面讲到的接口兼容性冲突
  • 技巧:将对象拆开,把每个属性看做一个个参数,则参数少的 f2 可以赋值给参数多的 f3

3. 返回值类型

返回值类型,只关注返回值类型本身即可:

简单类型的参数:

type F3 = () => string
type F4 = () => string
let f3: F3 = () => { return 'hello' }
let f4: F4 = f3 // 正常

type F5 = () => string
type F6 = () => number
let f5: F5 = () => { return 'hello' }
let f6: F6 = f5 // 报错

如果返回值类型是原始类型,此时两个类型要相同

复杂类型的参数:

type F7 = () => { name: string }
type F8 = () => { name: string, age: number }
let f7: F7 = () => { return { name: "zs" } }
let f8: F8 = f7 // 报错(成员少的不能给成员多的)

let f88: F8 = () => { return { name: "zs", age: 18 } }
let f77: F7 = f88 // 正常(成员多的可以给成员少的)

如果返回值类型是对象类型,此时成员多的可以赋值给成员少的(对象类型兼容的特点)

1.3 交叉类型

1.3.1 用法

交叉类型(&):功能类似于接口继承 extends,用于组合多个类型为一个类型(常用于对象类型)。

比如,

interface Person { name: string }
interface Person2 { phone: number }
type Person3 = Person & Person2
let obj: Person3 = {
    name: 'zs',
    phone: 123
} 

解释:使用交叉类型后,新的类型 Person3 就同时具备了 PersonPerson2 的所有属性类型。

相当于:

type Person3 = { name: string; phone: number }

1.3.2 交叉类型与接口继承的不同点

交叉类型(&) 和接口继承(extends) 的对比:

  • 相同点:都可以实现对象类型的组合

  • 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同

接口继承同名属性(基本数据类型):

interface Person{name: string}

interface Person2 extends Person{
    name: number // 报错
}

交叉类型同名属性(基本数据类型):

interface Person { uname: string }

interface Person2 { uname: number }

type Person3 = Person & Person2
let obj: Person3 = {
    uname: 1 // 报错
}

报错原因是:uname 既可以是 stringnumber 类型,这种混合的类型在类型机制不存在,所以为 never

接口继承同名属性(函数形参):

interface A{
    fn :(val: number) => string
}

interface B extends A{
    fn :(val: string) => string // 报错
}

交叉类型同名属性(函数形参):

interface A {
    fn: (val: number) => string
}

interface B {
    fn: (val: number) => string
}

type C = A & B
let obj: C = {
    fn(val: number | string) {
        return ''
    }
} // 正常

1.4 泛型

1.4.1 概述

泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。

需求:创建一个 id 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)

比如,id(10) 调用以上函数就会直接返回 10 本身。但是,该函数只接收数值类型,无法用于其他类型。为了能让函数能够接受任意类型,可以将参数类型修改为 any。但是,这样就失去了 TS 的类型保护,类型不安全。

泛型保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用

实际上,在 C#和 Java 等编程语言中,泛型都是用来实现可复用组件功能的主要工具之一。

1.4.2 创建泛型函数

function id<Type>(val: Type): Type {
    return val
}

解释:

  • 语法:在函数名称的后面添加 <>,尖括号中添加类型变量,比如此处的 Type

  • 类型变量 Type,是一种特殊类型的变量,它处理类型而不是值

  • 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)

  • 因为 Type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型

  • 类型变量 Type,可以是任意合法的变量名称

1.4.3 调用泛型函数

function id<Type>(val: Type): Type {
    return val
}

let num = id<number>(1) // number 类型
let str = id<string>('hello') // string 类型

解释:

  • 语法:在函数名称的后面添加 <>,尖括号中指定具体的类型,比如,此处的 number
  • 当传入类型 number 后,这个类型就会被函数声明时指定的类型变量 Type 捕获到
  • 此时,Type 的类型就是 number,所以,函数 id 参数和返回值的类型也都是 number

同样,如果传入类型 string,函数 id 参数和返回值的类型就都是 string

这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全。

1.4.4 简化调用泛型函数

未简化:

let num = id<number>(1) // number 类型
let str = id<string>('hello') // string 类型

简化:

let num = id(1) // number 类型
let str = id('hello') // string 类型

解释:

  • 在调用泛型函数时,可以省略 <类型> 来简化泛型函数的调用
  • 此时,TS 内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量 Type 的类型
  • 比如,传入实参 1,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型

推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读。

说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数。

1.4.5 泛型约束

泛型约束:默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性。

比如,id('a') 调用函数时获取参数的长度:

function id<Type>(val: Type): Type{
    console.log(val.length) // 报错
    return val
}

解释:Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length

此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)

添加泛型约束收缩类型,主要有以下两种方式:

  • 指定更加具体的类型
  • 添加约束

1. 指定更加具体的类型

比如,将类型修改为 Type[]Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了。

function id<Type>(val: Type[]): Type[] {
    console.log(val.length) // 报错
    return val
}

2. 添加约束

interface Ilength { length: number }

function id<Type extends Ilength>(val: Type): Type {
    console.log(val.length)
    return val
}

解释:

  • 创建描述约束的接口 ILength,该接口要求提供 length 属性
  • 通过 extends 关键字使用该接口,为泛型(类型变量)添加约束
  • 该约束表示:传入的类型必须具有 length 属性

注意:传入的实参(比如,数组)只要有 length 属性即可,这也符合前面讲到的接口的类型兼容性

1.4.6 多个泛型变量

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)。

比如,创建一个函数来获取对象中属性的值:

function getProp<Type, Key extends keyof Type>(obj: Type, key: Key): void {
    console.log(obj[key])
}

let person = {
    name: 'zs',
    age: 18
}

getProp(person, 'name') // zs
getProp(person, 'age') // 18
// getProp(person, 'phone') // 报错

getProp([1, 'jack', 'abc'], 2) // abc
getProp('abc', 'toUpperCase') // [Function: toUpperCase]

解释:

  • 添加了第二个类型变量 Key,两个类型变量之间使用 , 逗号分隔
  • keyof 关键字接收一个对象类型,生成其所有键的联合类型
  • 本示例中前面的 keyof Type 实际上获取的是 person 对象所有键的联合类型,也就是:'name' | 'age'
  • 本示例中后面的 keyof Type 实际上获取的是 js 对象或基础数据类型的所有键的联合类型,也就是原本就能使用的方法,且第二个参数为数字时,代表索引
  • 类型变量 KeyType 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性

1.4.7 泛型接口

1. 使用

泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性。

interface IdFunc<Type> {
    id: (val: Type) => Type
    ids: () => Type[]
}

let obj: IdFunc<number> = {
    id(val) { return val },
    ids() { return [1, 2, 3] }
}

解释:

  • 在接口名称的后面添加 <类型变量>,那么,这个接口就变成了泛型接口
  • 接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量
  • 使用泛型接口时,需要显式指定具体的类型(比如,此处的 IdFunc<nunber>
  • 此时,id 方法的参数和返回值类型都是 numberids 方法的返回值类型是 number[]

2. 数组在 TS 中就是一个泛型接口

实际上,JS 中的数组在 TS 中就是一个泛型接口。

et num = [1, 2, 3] // num 是 num[] 类型
let str = ['a', 'b', 'c'] // str 是 string[] 类型
let x = [1, 'a', true] // x 是 (string | numbner | boolean)[] 类型

解释:当我们在使用数组时,TS 会根据数组的不同类型,来自动将类型变量设置为相应的类型

1.4.8 泛型类

class 也可以配合泛型来使用。

1. 创建实例必须指定明确的类型

创建泛型类:

class Car<Type>{
    num: Type
    add: (x: Type, y: Type) => Type
}

const myCar = new Car<number>() //  创建实例必须指定明确的类型
myCar.num = 12345

解释:

  • 类似于泛型接口,在 class 名称后面添加 <类型变量>,这个类就变成了泛型类
  • 此处的 add 方法,采用的是箭头函数形式的类型书写方式

类似于泛型接口,在创建 class 实例时,在类名后面通过 <类型> 来指定明确的类型。

2. 创建实例不用指定明确的类型

class Car<Type>{
    num: Type
    add: (x: Type, y: Type) => Type
    constructor(num: Type){
        this.num = num
    }
}

const myCar = new Car<number>(12345)

通过构造函数和类型推断可以实现创建实例的时候,不用指定明确的类型,具体类型根据创建实例所传入的值决定。

1.4.9 泛型工具类型

1. 简介

泛型工具类型:TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作。

说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。

这些工具类型有很多,主要学习以下几个:

  • Partial<Type>

  • Readonly<Type>

  • Pick<Type, Keys>

  • Record<Keys, Type>

2. Partial<Type>

泛型工具类型 - Partial<Type> 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。

interface Props {
    id: number
    arg: number[]
}

type P = Partial<Props>

在这里插入图片描述

解释:构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的。

3. Readonly<Type>

泛型工具类型 - Readonly<Type> 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。

interface Props2 {
    id: number
    arg: number[]
}

type P2 = Readonly<Props2>
let p2: P2 = { id: 1, arg: [1, 2, 3] }
p2.id = 2 // 报错

解释:构造出来的新类型 P2 结构和 Props2 相同,但所有属性都变为只读的。

当我们想重新给 id 属性赋值时,就会报错:无法分配到 "id" ,因为它是只读属性。

4. Pick<Type>

泛型工具类型 - Pick<Type, Keys>Type 中选择一组属性来构造新类型。

interface Props3 {
    id: number
    arg: number[]
    arg2: string[]
}

type P3 = Pick<Props3, 'id' | 'arg2'>

在这里插入图片描述

解释:

  • Pick 工具类型有两个类型变量:
    • 表示选择谁的属性
    • 表示选择哪几个属性
  • 其中第二个类型变量,如果只选择一个则只传入该属性名即可
  • 第二个类型变量传入的属性只能是第一个类型变量中存在的属性
  • 构造出来的新类型 P3 ,只有 idarg2 两个属性类型

5. Record<Type>

4.4 泛型

泛型工具类型 - Record<Keys,Type> 构造一个对象类型,属性键为 Keys,属性类型为 Type

type P4 = Record<'num1' | 'num2' | 'num3', number[]>

在这里插入图片描述

解释:

  • Record 工具类型有两个类型变量:
    • 表示对象有哪些属性
    • 表示对象属性的类型
  • 构建的新对象类型 P4 表示:这个对象有三个属性分别为:num1num2num3,属性值的类型都是 number[]

1.5 索引签名类型

1.5.1 基本使用

绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。

使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就用到索引签名类型了。

interface Person {
    [key: string]: number
}

let obj: Person = {
    age: 1,
    height: 111
}

解释:

  • 使用 [key: string] 来约束该接口中允许出现的属性名称。表示只要是 string 类型的属性名称,都可以出现在对象中
  • 这样,对象 obj 中就可以出现任意多个属性(比如:ageheight 等)
  • key 只是一个占位符,可以换成任意合法的变量名称
  • 隐藏的前置知识:JS 中对象({})的键是 string 类型的

1.5.2 和泛型一起使用

在 JS 中数组是一类特殊的对象,特殊在数组的键(索引)是数值类型。

并且,数组也可以出现任意多个元素。所以,在数组对应的泛型接口中,也用到了索引签名类型。

interface MyArray<T> {
    [key: number]: T
}

let obj2: MyArray<number> = [1, 3, 5]
console.log(obj2[1]) // 3

解释:

  • MyArray 接口模拟原生的数组接口,并使用 [n: number] 来作为索引签名类型
  • 该索引签名类型表示:只要是 number 类型的键(索引)都可以出现在数组中,或者说数组中可以有任意多个元素
  • 同时也符合数组索引是 number 类型这一前提

1.6 映射类型

1.6.1 联合类型和映射类型

映射类型:基于旧类型创建新类型(对象类型),减少重复、提升开发效率。

比如,类型 A 有 x、y、z 三个属性,另一个类型 B 中也有这三个属性,并且类型 B 中这三个属性类型相同:

type A = 'x' | 'y' | 'z'
type B = {
    x: number
    y: number
    z: number
}

这样书写没错,但 x、y、z 重复书写了两次。像这种情况,就可以使用映射类型来进行简化。

type A = 'x' | 'y' | 'z'
type C = { [key in A]: number }

解释:

  • 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了 []
  • Key in A 表示 Key 可以是 A 联合类型中的任意一个
  • 注意:映射类型只能在类型别名中使用,不能在接口中使用

1.6.2 对象和映射类型

映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建:

type o = {
    name: string,
    age: number,
    weight: number
}

type B = { [key in keyof o]: number }

解释:

  • 首先,先执行 keyof o 获取到对象类型 o 中所有键的联合类型即,'name' | 'age' | 'weight'
  • 然后,key in ... 就表示 key 可以是对象 o 中所有的键名称中的任意一个
  • 注意如示例这样指定会使新类型 B 的所有属性是同一个类型

1.6.3 泛型工具与接口

快速生成属性值相同的类型,我们除了映射类型之外,也可以想到刚刚说过的泛型工具。

实际上,前面讲到的泛型工具类型(比如 Partial<Type>)都是基于映射类型实现的。

Partial<Type> 的实现:

// 实现: 这里的 Type 是个对象
type Partical<Type> = {
    [key in keyof Type]?: Type[key]
}

interface Props {
    id: number
    arg: number[]
}

type P = Partial<Props>

解释:

  • keyof Typekeyof Props 表示获取 Props 的所有键,也就是:'id' | 'arg'
  • [] 后面添加 ?,表示将这些属性变为可选的,以此来实现 Partial 的功能
  • 冒号后面的 Type[key] 表示获取 Type 中每个键对应的类型。比如,如果是 id 则类型是 number;如果是 arg 则类型是 number[]
  • 最终,新类型 P 和旧类型 Props 结构完全相同,只是让所有类型都变为可选了

1.6.4 索引查询

刚刚用到的 Type[P] 语法,在 TS 中叫做索引查询(访问)类型。

作用:用来查询属性的类型。

type Props = {
    a: number,
    b: string,
    c: boolean
}

type A = Props['a'] // number
type B = Props['b'] // string
type C = Props['c'] // boolean
type D = Props['d'] // 报错

解释:Props['a'] 表示查询类型 Props 中属性 'a' 对应的类型 number。所以,A 的类型为 number

注意:[] 中的属性必须存在于被查询类型中,否则就会报错。

1.6.5 多索引查询

索引查询类型的其他使用方式:同时查询多个索引的类型。

type Props = {
    a: number,
    b: string,
    c: boolean
}

type A = Props['a' | 'b'] // string | number
type B = Props[keyof Props] // string | number | boolean

解释:

  • 使用字符串字面量的联合类型,获取属性 ab 对应的类型,结果为: string | number
  • 使用 keyof 操作符获取 Props 中所有键对应的类型,结果为: string | number | boolean

本文主要学习 黑马程序员前端TypeScript教程,TypeScript零基础入门到实战全套教程

如有错误,敬请指正,欢迎交流🤝,谢谢♪(・ω・)ノ