TypeScript高级部分

101 阅读9分钟

#TypeScript高级类型

1. 概述

TS中的高级类型很多,重点学习以下类型:

  1. class类
  2. 类型兼容性
  3. 交叉类型
  4. 泛型和keyof
  5. 索引签名类型和索引查询类型
  6. 映射类型

2. class类

TS全面支持ES中的class关键字,并为其添加类型注解和其他语法

class基本使用如下:

class Person{

}

const p = new Person()

2.1 构造器

1. 构造器是用来初始化类的实例的。类的成员必须初始化之后,才可以通过 this.成员名称 来访问实例成员 2. 需要为构造函数指定类型注解,否则会被隐式推断为 any ;构造函数不需要返回值类型

class Person{

    age: number;
    name: string;

    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
}

const person = new Person('wjw',18)

注意:TS不允许一个类中有多个构造器(JAVA里面是可以的)

2.2 实例方法

类里面的实例方法的类型注解(参数和返回值)与函数用法相同

class Animal{
    public name: string = 'helloworld';
    public age: number = 0;

    public drink(water:string){
        console.log(water);
    }
}
new Animal().drink("可口可乐")

2.3 类的继承

类继承的两种方式:1. extends(继承父类) 2. implements(实现接口) 子类继承父类,子类就有了父类和子类的所有属性和方法

class Animal{
    public name: string = 'helloworld';
    public age: number = 0;

    public drink(water:string){
        console.log(water);
    }
}
class Dog extends Animal{   //extends继承父类
    public eat(food:string){
        console.log(`food: ${food}`); 
    }    
}

let dog = new Dog()
dog.eat('热狗')
dog.drink('可乐')
interface IAnimal{
    sing():void
}

class Animal2 implements IAnimal{
    sing(): void {
        console.log('animal implement IAnimal');
    }
}

new Animal2().sing()

2.4 类成员的可见性

可以使用TS来控制class的方法或属性对于class外的代码是否可见 可见性修饰符包括:1. public(公有的) 2. protected(受保护的) 3. private(私有的)

解释:

  1. 在类属性或方法前面添加public关键字,来修饰该属性或方法是共有的
  2. 因为public是默认可见性,所以,可以直接省略
  3. protected: 表示受保护的,仅对于其申明所在类和子类中可见,但是子类的实例对象是不可见的
class Animal{
    public name: string = 'helloworld';
    public age: number = 0;

    protected drink(water:string){
        console.log(water);
    }
}
new Animal().drink("可口可乐")  //报错,属性“drink”受保护,只能在类“Animal”及其子类中访问,不能在实例对象中使用
  1. private:表示私有的,只在当前类可见,对实例对象以及子类都是不可见的
范围publicprotectedprivate
当前类可见可见可见
子类可见可见不可见
当前类实例对象可见不可见不可见
子类实例对象可见不可见不可见
  1. 只读修饰符: 表示只读,用来防止在构造函数中外对属性进行赋值,并且只能修饰属性不能修饰方法。在接口中也可以使用readonly
class Person{
    readonly name: string = 'wjw'
    constructor(name: string){
        this.name=name
    }
}
interface ITest{
   readonly name: string
}

let obj: ITest ={
    name: "jack"
}

obj.name='mary'   //报错,无法为“name”赋值,因为它是只读属性

3. 类型兼容性

TS是结构化类型系统,对于结构化类型系统,如果两个对象具有相同的形状,则认为他们属于同一类型

上述说法其实并不准确,更准确的说法:对于对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给成员少的),也就是超集的关系可以进行兼容赋值

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

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

  const p:Point = new Point3D();//只有Pointe3D能满足Point类型要求的,那么就可以说Point兼容Point3D

Point3D的成员至少与Point相同,则Point兼容Point3D。多一个属性就多一个约束,小狗是动物吗?是!动物是小狗吗?不是

4. 函数兼容性

函数的兼容性比较复杂,需要考虑:1. 参数个数 2. 参数类型 3. 返回值类型

  1. 参数个数:参数个数多的兼容参数个数少的(参数少的可以赋值给多的)
type F1=(x:number) => void
type F2 = (x:number, y:number, z:number) => void

let f1:F1 = (x)=>console.log(x);
let f2:F2 = f1  //参数少的可以赋值给参数多的
  1. 参数类型:相同位置的参数类型要相同(原始类型)或者类型要兼容(对象类型)
class Father {
   name:string
   age:number
}

class Son {
   name:string
   age:number
   address:string
}

type F3 = (p:Father) => void
type F4 = (p:Son) => void


let f3!:F3
let f4!:F4

// f3=f4   //不能将类型“F4”分配给类型“F3”。
       //参数“p”和“p” 的类型不兼容。
       //类型 "Father" 中缺少属性 "address",但类型 "Son" 中需要该属性

判断函数兼容技巧:将对象拆开,把每个属性看作一个个参数,参数少的可以赋值给参数多的

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

解释:

  • 如果返回值类型是原始类型,此时两个类型要相同。
  • 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,因为成员多的里面包含了成员少的

5. 类型守卫

  • typeof 原始类型保护
  • instanceof class类型保护
  • in 类型保护
  • 自定义类型保护

注意:typeof只能给出原始类型(如 number、string、object 等),而不能给出接口或类的类型

type FunB=(a:number|string)=>void

let funTypeof:FunB=(a)=>{
    if(typeof a=='string'){
        console.log('funTypeof is string'); 
    }
    else if(typeof a=='number'){
        console.log('funTypeof is number');
    }
}

funTypeof(123)      //funTypeof is number
funTypeof('123')    //funTypeof is string
class IA{
    IAdd(i: number):void{

    }
}

class IB{
    IBdd(num:number):void{

    }
}

type FunA=(arg:IA|IB)=>void

let funInstanceof:FunA =(args)=>{
    if(args instanceof IA){
        args.IAdd(1);
    }else if(args instanceof IB){
        args.IBdd(1);
    }
}
class C{
    name: string='wjw'
    age: number=12
}
class D{
    address: string='xian'
    age: number=10
}

function functionIn(args: C|D){
    if('name' in args){
        console.log("name in C"); 
    }
    else if('address' in args){
        console.log("address in D");
    }
}

functionIn(new C())

6. 交叉类型

将多个类型合并为一个类型 符号:&

class A{
    name: string
}

class B{
    age: number
}

let stu:A&B={  //类的交叉
    name:'wjw',
    age: 10
}


interface IA{
    name: string
}
interface IB{
    age:number
    sayHi(): void
}

let stu2:IA&IB={  //接口的交叉
    name:'wjw',
    age: 10,
    sayHi(){
        
    }
}

7.联合类型

定义:声明的类型不确定 语法:|

//基本联合类型
let a:number|string = 10;
let size: 'BIG'|'SMALL'|'MIDDLE'='BIG'

//可辨式联合类型
interface Square{
    kind:'正方形',
    size: number
}
interface Rectangle{
    kind:'长方形',
    width: number,
    height: number
}
interface Circle{
    kind:'圆形',
    radius:number
}

type Shape = Square|Rectangle|Circle

function getArea(shape:Shape){
    switch(shape.kind){
        case '正方形':{
            return shape.size*shape.size
        }
        case '长方形':{
            return shape.width*shape.height
        }
        case '圆形':{
            return 3.14*shape.radius*shape.radius
        }
        default: 
        throw new Error("Shape Error")
    }
}

console.log("面积是:"+getArea({kind:'长方形',width: 5,height: 10}));

8. 索引操作符

  • 索引查询操作符:keyof T
    • 表示提取T类型的所有公共属性,组成字面量联合类型
  • 索引访问操作符:T[k]
    • 表示对象T的属性K声明的类型 keyof是为了取得对象的key值组成的联合类型
//查询操作符
interface Person{
    name: string;
    age: number;
    sayHi():void
}

let k: keyof Person;
k='age' //k可以是Person里面的任意一个属性名:'name'|'age'|'sayHi'
k='sayHi'

//访问操作符 T[K]

let k2:Person['name']    //k2的类型就是Person类里面name属性的类型 : string

9. 泛型约束extends

使用extends关键字来约束泛型的类型范围,extends后面可以是原始类型、类、接口、联合类型、交叉类型

//约束为原始类型
function F1 <T extends any>(x:T){
    console.log(typeof x);
}
F1(123)    //number

//约束为对象类型,要求泛型所代表的类型里面必须含有C1类的所有属性
class C1{
    length:number=10;
}

function F2<T extends C1>(x:T){
    console.log(x.length);    
}

F2({length:12})

//约束对象为接口类型,要求泛型所代表的类型满足接口I1
interface I1{
    length:number;
}

function F3<T extends I1>(x:T){
    console.log(x.length);
    
}

F3({length:20})

//约束类型为联合类型,要求泛型所代表的类型不是number,就是string
type T1 = number|string

function F4<T extends T1>(x:T[]){
    x.map((item)=>{
        console.log(item);   
    })    
}

F4([1,'2',3,'4'])

image.png

10. 索引签名类型

绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并且为对象添加准确的类型.但是当我们无法确定对象中有哪些属性(或者说无法确定对象中可以出现任意多个属性),此时,就用到索引签名类型了

解释:

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

let obj:AnyObject = {
    a:1,
    b:2, 
}

在JS中数组是一类特殊的对象,特殊在数组的键(索引)是数值类型,并且,数组也可以出现任意多个元素,所以,在数组对应的泛型接口中,也用到了索引签名类型

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


let arr:MyArray<number> = [1,3,56]

11. 映射类型

映射类型:基于旧类型创建新的类型(对象类型),减少重复,提升开发效率 比如,类型PropsKeys有x/y/z,另外一个类型Type1中也有x/y/z,并且Type1中的x/y/z的类型相同:

type PropsKeys = 'x'|'y'|'z'
type Type1 = {x:number,y:number,z:number},

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

type PropsKeys = 'x'|'y'|'z'
type Type2 = { [Key in PropsKeys]:number }
//等价于
type Type2 = {x: number, y: number,z: number}

解释:

  • 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]
  • Key in PropsKeys 表示Key可以是PropsKeys联合类型中的任意一个,类似于forin(let key in obj)
  • 使用映射类型创建的相对性类型Type2和Type1结构完全相同

映射类型不能在接口中使用,只能在type中使用

映射类型联合keyof使用

type Props = {a:number, b:number, c:string, d:number}
type Type3 = {[key in keyof Props]:number}