陪我看月亮,陪我学习 TypeScript,好吗?

1,980 阅读22分钟

我正在参与掘金创作者训练营第6期 点击了解活动详情

0、TypeScript简介

  1. TypeScript是JavaScript的超集。

  2. 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。

  3. TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。

  4. TS完全兼容JS,换言之,任何的TS代码都可以直接当成JS使用。

  5. 为什么需要typeScript

    • 在编译期间就可以发现错误
    • 提高开发体验,有代码提示
    • 强大类型系统 ,提高代码可维护性
    • 支持最新的ES特性
    • 面向对象
    • 各大框架的加持
  6. TypeScript练习题 github.com/type-challe…

1、TypeScript 开发环境搭建

  1. 下载Node.js

  2. 安装Node.js

  3. 使用npm全局安装typescript

    • 进入命令行
    • 输入:npm i -g typescript
    • 检查是否安装成功 命令行输入 tsc
  4. 创建一个ts文件

  5. 使用tsc对ts文件进行编译

    • 进入命令行
    • 进入ts文件所在目录
    • 执行命令:tsc xxx.ts

2、基本类型

Ts在js已有类型上增加了 ==> 元组,枚举,any,类型断言,void,never

  • 类型注解

    • 简而言之,类型注解给变量设置了类型,使得变量只能存储某种类型的值

    • 语法:

      • let 变量: 类型;
        ​
        let 变量 = 值; //变量的声明和赋值时同时进行的,可以省略掉类型声明function fn(参数: 类型, 参数: 类型): 类型{
            ...
        }
        
  • 自动类型判断

    • TS拥有自动的类型判断机制
    • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
    • 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明

2.1 类型

类型例子描述
number1, -33, 2.5任意数字
string'hi', "hi"任意字符串
booleantrue、false布尔值true或false
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
object{name:'孙悟空'}任意的JS对象
array[1,2,3]任意JS数组
tuple[4,5]元组,TS新增类型,固定长度数组
enumenum{A, B}枚举,TS中新增类型

2.2 原始数据类型

  • number/string/boolean/null/undefined
  • 特点:简单,这些类型,完全按照 JS 中类型的名称来书写
let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false
let obj: null = null   //null 类型的值只能是 null
let und: undefined = undefined   //  undefined 类型的值只能是 undefined

2.3 array (数组类型)

let list: number[] = [1, 2, 3];// 写法一:
let list: Array<number> = [1, 2, 3]; // 写法二:

2.4 字面量

  • 使用模式:字面量类型配合联合类型一起使用

  • 使用场景:用来表示一组明确的可选值列表

  • 结论:相比于string类型,使用字面量类型更加精确,严谨

    // 使用自定义类型:
    type Direction = 'up' | 'down' | 'left' | 'right'function changeDirection(direction: Direction) {
      console.log(direction)
    }
    ​
    // 调用函数时,会有类型提示:
    changeDirection('up')
    

2.5 any(我不在乎它的类型)

如果声明变量不指定类型,则TS解析器会自动判断变量的类型any (又称隐式的any)

  • 在实际开发过程中不建议使用any , 因为失去了TypeScript最大的类型系统

  • 使用any 之后,不会有任何类型错误提示,即使可能存在错误

    let d: any = 4;
    d = 'hello';
    d = true;
    
  • 除非:临时使用any 来避免书写很长,很复杂的类型

2.6 unknown (我不知道它类型)

表示 未知类型的值

  • let notSure: unknown = 4;
    notSure = 'hello';
    

2.7 never

  • function error(message: string): never {
      throw new Error(message);
    }
    

2.8 object(没啥用)

  • let obj: object = {};
    let b:{name:string} //语法 {属性名:属性值}用来指定对象中可以包含哪些属性//在属性名后面加上 ? 表示属性是可选的
    let b:{name:string,age?:number};
     b={name:'孙悟空'age:18}
    ​
    //[propName:string]:any 表示任意类型的属性
    let c:{name:string,[propName:string]:any}
    c={name:'猪八戒'age:18,gender:'男'}
    

2.9 tuple(元组 )

场景:当我们想定义一个数组中具体索引位置的类型时,可以使用元组

  • 元组是另一种数组类型,只有确切的知道包含多个元素,以及对应的类型

  • 元组一旦指定,数组个数和对应位置的类型必须匹配

    let x: [string, number]=["hello", 10];
    
    //元组,元组就是固定长度的数组
      //语法:[类型,类型,类型]
      let h:[string,number];
      h=['hello',123]
    

2.10 enum(枚举)

使用 enum 关键字定义一组常量,它描述一组明确的可选值,

  • 数字枚举

    • 枚举成员的值为数字的枚举,称为:数字枚举
    • 枚举成员是有值的,默认从0开始自增的数值
    enum Color {
    Red,
    Green,
    Blue,
    }
    //类似于JS中的对象,直接通过点(.)语法访问枚举的成员
    let c: Color = Color.Green; 
    
  • 字符串枚举

    • 枚举成员的值是字符串
    • 字符串枚举没有自增长行为,因此字符串枚举的每个成员必须有初始值
    eunm Direction {
       Up="Up"
       Down='DOWN'
       Left='LEFT'
       Right="RIGHT"
    }
    
  • 枚举成员的值可以进行重新初始化的

    //可以给枚举中的成员初始化值
    enum Color {
    Red = 1,
    Green,
    Blue,
    }
    let c: Color = Color.Green;
    
  • 实际开发中:更加推荐使用字面量+联合类型组合的方式

  • 约定枚举名称,枚举中的值以大写字母开头

    /使用以函数参数
    eunm Direction {
       Up,
       Down,
       Left,
       Right
    }
    ​
    function changeDirection(direction:Direction){}
    //类似于JS中的对象,直接通过点(.)语法访问枚举的成员
    changeDirection(Direction.Left)
    

2.11 symbol

作用 :可以作为对象属性的键,防止与对象中其他键冲突

  • Symbol() 创建的值是唯一的

    let uniqkey:symbol=Symbol()
    ​
    let obj={
      a:'123',
      [uniqkey]:100
    }
    ​
    console.log(obj[uniqkey])
    

2.11 联合类型

  • 如果定义的数据既可以是 number 类型,又可以是 string 类型

  • 可以使用联合类型 :由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种

    //数据中使用联合
    let arr:Array<number | string>=[1,2,'张三']
    ​
    //简化写法 此处()的目的是为了提升优先级,表示:number 类型的数组或 string类型的数组
    let arr:(number|string)[]=[1,'a',3,'b']
    ​
    // 应用:定义一个定时器变量
    let timer: number | null = null
    timer = setInterval(() => {}, 1000)
    

2.12 类型别名(自定义类型)

场景:当同一类型(复杂)被多次使用时,可以通过类型别名,方便复用

  • 语法:type 类型名称=具体类型

  • 推荐:大驼峰命名方式

  • 创建类型别名后,直接使用该类型别名作为变量的类型注解即可

    type Mng=number | string
    let my:Mng='1'type CustomArray=(number|string)[]
    let arr1:CustomArray=[1,'q',2,'f']
    
  • type可以替换 interface

    type Person={
      name:string
      age:number
    }
    ​
    let p:Person={
      name:'cp',
      age:18
    }
    

2.13 字面量类型配合联合类型

字面量类型配合联合类型一起使用,用来表示一组明确的可选值列表

  • 通过const 定义,有具体的值,这个值可以作为类型

  • 相比于string 类型,使用字面量更加严谨和精确

  • 场景 :组件props校验 限制用户传参的格式,提供几个固定的选项

    //使用自定义类型
    type Direction ='up'|'down'|'left'|'right'function changeDirection(direction:Direction){
      console.log(direction)
    }
    ​
    // 调用函数时,会有类型提示: 参数只能是 up/down/left/right中的任意一个
    changeDirection('up')
    

2.14 类型推论

TypeScript 里没有明确指出类型的地方,类型推断会帮助提供类型

  • 注意 类型推论有时候不够精确,需要手动声明或者使用类型断言

  • 能省略类型注解的地方就省略(偷懒,充分利用TS类型推论的能力,提升开发效率)

  • 技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型

    let x=100 //x 自动推断为number
    let y='200' //y 自动推断为stringfunction add(a:number,b:number){
      return a+b
    }
    let z= add(1,2) // z自动推断为 number
    

2.15 非空断言(!.)

如果我们明确的知道对象的属性一定不会为空,那么可以使用非空断言 !

  • 作用:如果明确知道对象的属性一定不为空,可以使用来指定

  • 语法

    const imgRef = ref<HTMLImageElement | null>(null)
    ​
    onMounted(() => {
      imgRef.value!.src = '2.jpg' //!. 非空断言
    })
    

注意:

  • 非空断言一定要确保有该属性才能使用,不然使用非空断言会导致bug

2.16 类型断言

语法: 变量 as 类型

​ <类型> 变量

🍌基本使用

  • 定义一个变量,默认存放空对象
  • 对象中的属性来源于网络请求
  • 使用对象中的属性

可以使用类型断言来指定更具体的类型

type Use = {
  name: string
  age: number
  gender: string
}
// 指定类型之后,直接给 use 赋值空对象会报错
// 我们明确知道 use 对象就是 Use 类型
// 可以通过 as 来将空对象转为 use,以达到显示提示的目的
let use:Use = {} as Use
// 另一种用法
// let use:Use = <Use>{}
setTimeout(() => {
  use = {
    name: 'zs',
    age: 18,
    gender: '男'
  }
}, 1000);
console.log(use.name)

🍉操作Dom

  • 类型断言还在 dom 操作时用的比较多

    // 直接获取 dom 之后,无法点出 link 中的属性
    const aLink = document.getElementById('link')
    ​
    // 获取 dom 之后,将 link 断言为 HTMLAnchorElement,可以轻松点出属性
    const aLink = document.getElementById('link') as HTMLAnchorElement
    

3.函数类型

函数的类型实际上指的是:函数的参数和返回值的类型

3.1 指定参数、返回值的类型

  • 函数参数 & 返回值 的类型

    // 函数声明
    function add(num1: number, num2: number): number {
      return num1 + num2
    }
    ​
    // 箭头函数
    const add = (num1: number, num2: number): number => {
      return num1 + num2
    }
    

3.2 指定整个函数类型

  • 函数的类型完全一致,可以将函数类型进行封装

    type AddFn = (num1: number, num2: number) => numberconst add: AddFn = (num1, num2) => {
      return num1 + num2
    }
    

3.3 void 类型

如果一个函数没有返回值,此时,在 TS 的类型中,应该使用 void 类型

  • 如果函数没有返回值,那么,函数返回值类型为:void

    function greet(name: string): void {
      console.log('Hello', name)
    }
    
      // 如果什么都不写,此时,add 函数的返回值类型为: void
      const add = () => {}
      // 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
      const add = (): void => {}
      ​
      // 但,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以
      const add = (): undefined => {
        // 此处,返回的 undefined 是 JS 中的一个值
        return undefined
      }
    

3.4 可选参数

定义函数时,有些参数可传可不传,这种情况下就需要使用可选参数来指定类型

  • 该函数有两个参数,其中的age参数在类型前面加了一个?,代表可选参数

  • 注意 可选参数要放到参数列表的最后

  • 比如,数组的 slice 方法,可以 slice() 也可以 slice(1) 还可以 slice(1, 3)

    //示例1
    function showPersonIfo(name:string,age?:number) { 
      console.log(name,age)
    }
    ​
    //示例2
    function mySlice(start?: number, end?: number): void {
      console.log('起始索引:', start, '结束索引:', end)
    }
    

4.对象类型

4.1 基本使用

  1. 使用 {} 来描述对象结构

  2. 属性采用属性名: 类型的形式

  3. 方法采用方法名(): 返回值类型的形式

    // 空对象
    let person: {} = {}
    ​
    // 有属性的对象
    let person: { name: string } = {
      name: '同学'
    }
    ​
    // 既有属性又有方法的对象
    // 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
    let person: { name: string; sayHi(): void } = {
      name: 'jack',
      sayHi() {}
    }
    ​
    // 对象中如果有多个类型,可以换行写:
    // 通过换行来分隔多个属性类型,可以去掉 `;`
    let person: {
      name: string
      sayHi(): void
    } = {
      name: 'jack',
      sayHi() {}
    }
    

4.2 任意属性

  • 任意属性来实现可以少属性/多属性

    interface Person {
        name: string;
        age?: number;
        [propName: string]: any;
    }
    ​
    let tom: Person = {
        name: 'Tom',
        gender: 'male'
    };
    ​
    

4.3 箭头函数参数类型

  • 对象中箭头函数形式的参数类型定义

  • 方法的类型也可以使用箭头函数形式

    {
        greet(name: string):string,
        greet: (name: string) => string
    }
    ​
    type Person = {
      greet: (name: string) => void
      greet(name: string):void
    }
    ​
    let person: Person = {
      greet(name) {
        console.log(name)
      }
    }
    

4.4 对象可选属性

  • 对象的属性或方法,也可以是可选的,此时就用到可选属性

  • 比如,我们在使用 axios({ ... }) 时,如果发送 GET 请求,method 属性就可以省略

  • 可选属性的语法与函数可选参数的语法一致,都使用 ? 来表示

    type Config = {
      url: string
      method?: string
    }
    ​
    function myAxios(config: Config) {
      console.log(config)
    }
    

4.5 使用类型别名

  • 不推荐:直接使用 {} 形式为对象添加类型,会降低代码的可读性(不好辨识类型和值)

  • 推荐:使用类型别名为对象添加类型

    // 创建类型别名
    type Person = {
      name: string
      sayHi(): void
    }
    ​
    // 使用类型别名作为对象的类型:
    let person: Person = {
      name: 'jack',
      sayHi() {}
    }
    

5.类(class)

5.1 定义类

class 类名 {
  属性名: 类型;
  
  constructor(参数: 类型){
    this.属性名 = 参数;
  }  
  方法名(){
    ....
  }
}

🍌示例

class Person{
    name: string;
    age: number;
   //构造函数 构造函数会在对象创建时调用
    constructor(name: string, age: number){
      //在实例方法中,this就表示当前的实例
        this.name = name;
        this.age = age;
    }
​
    sayHello(){
        console.log(`大家好,我是${this.name}`);
    }
}

🍎 使用类

const p = new Person('孙悟空', 18); 
p.sayHello();

5.2 readonly(只读属性)

  • 如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改

  • 在通过const 进行常量声明,声明后不可以修改,但是如果值是一个引用类型的话,依旧可以对其内部的发展进行修改。所以在使用Typescript当中的readonly关键字对属性或者是变量进行声明,那么将会在编译时就发出告警

    const a:{readonly name:string}={name:'user'}
    ​
    a.name='1111'//会报错
    

    1655964462843.png

5.3 public (默认值)

  • 可以在类,子类和对象中修改

    class Person{
        public name: string; // 写或什么都不写都是public
        public age: number;
    ​
        constructor(name: string, age: number){
            this.name = name; // 可以在类中修改
            this.age = age;
        }
    ​
        sayHello(){
            console.log(`大家好,我是${this.name}`);
        }
    }
    ​
    class Employee extends Person{
        constructor(name: string, age: number){
            super(name, age);
            this.name = name; //子类中可以修改
        }
    }
    ​
    const p = new Person('孙悟空', 18);
    p.name = '猪八戒';// 可以通过对象修改
    

5.4 protected

  • 可以在类、子类中修改

    class Person{
        protected name: string;
        protected age: number;
    ​
        constructor(name: string, age: number){
            this.name = name; // 可以修改
            this.age = age;
        }
    ​
        sayHello(){
            console.log(`大家好,我是${this.name}`);
        }
    }
    ​
    class Employee extends Person{
    ​
        constructor(name: string, age: number){
            super(name, age);
            this.name = name; //子类中可以修改
        }
    }
    ​
    const p = new Person('孙悟空', 18);
    p.name = '猪八戒';// 不能修改
    

5.5 private

  • 可以在类中修改

    class Person{
        private name: string;
        private age: number;
    ​
        constructor(name: string, age: number){
            this.name = name; // 可以修改
            this.age = age;
        }
    ​
        sayHello(){
            console.log(`大家好,我是${this.name}`);
        }
    }
    ​
    class Employee extends Person{
    ​
        constructor(name: string, age: number){
            super(name, age);
            this.name = name; //子类中不能修改
        }
    }
    ​
    const p = new Person('孙悟空', 18);
    p.name = '猪八戒';// 不能修改
    
  • 属性存取器

    • 对于一些不希望被任意修改的属性,可以将其设置为private

    • 直接将其设置为private将导致无法再通过对象修改其中的属性

    • 我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器

    • 读取属性的方法叫做setter方法,设置属性的方法叫做getter方法

    • 示例:

      class Person{
          private _name: string;
      ​
          constructor(name: string){
              this._name = name;
          }
      ​
          get name(){
              return this._name;
          }
      ​
          set name(name: string){
              this._name = name;
          }
      ​
      }
      ​
      const p1 = new Person('孙悟空');
      console.log(p1.name); // 通过getter读取name属性
      p1.name = '猪八戒'; // 通过setter修改name属性
      

5.6 static (静态属性)

使用static关键词标记的属性会变成类的静态成员,这些属性只存在于类本身

  • 使用静态属性无需创建实例,通过类即可直接使用

    class Tools{
        static PI = 3.1415926;
        
        static sum(num1: number, num2: number){
            return num1 + num2
        }
    }
    ​
    console.log(Tools.PI);
    console.log(Tools.sum(123, 456));
    

5.7 extends (继承)

  • 通过继承可以将其他类中的属性和方法引入到当前类中

  • 通过继承可以在不修改类的情况下完成对类的扩展

  • 重写

    • 发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写

    • 示例

      class Animal{
          name: string;
          age: number;
      ​
          constructor(name: string, age: number){
              this.name = name;
              this.age = age;
          }
      ​
          run(){
              console.log(`父类中的run方法!`);
          }
      }
      ​
      class Dog extends Animal{
      ​
          bark(){
              console.log(`${this.name}在汪汪叫!`);
          }
      ​
          run(){
              console.log(`子类中的run方法,会重写父类中的run方法!`);
          }
      }
      ​
      const dog = new Dog('旺财', 4);
      dog.bark(); //旺财在汪汪叫!
      

5.8 super

  • 在子类中可以使用super来完成对父类的引用

    class Animal{
        name: string;
        age: number;
    ​
        constructor(name: string, age: number){
            this.name = name;
            this.age = age;
        }
       //在类的方法中,super就表示当前类的父类
        sayHello(){
            console.log(`动物在叫!`);
        }
    }
    ​
    class Dog extends Animal{
         //如果在子类中写了构造函数,在子类构造函数中必须调用父类构造函数
        constructor(name: string, age: number){
          super(name)//调用父类的构造函数 
        }
        sayHello(){
           super.sayHello();
        }
    }
    ​
    const dog = new Dog('旺财', 4);
    dog.sayHello(); //动物在叫
    

5.9 abstract (抽象类)

  • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例

    abstract class Animal{
        abstract run(): void;
        bark(){
            console.log('动物在叫~');
        }
    }
    ​
    class Dog extends Animals{
        run(){
            console.log('狗在跑~');
        }
    }
    

🍎this

  • 在类中,使用this表示当前对象

6 interface (接口)

当一个对象类型被多次使用时,可以使用 type 来描述对象的类型,达到复用的目的

  • 定义一个interface,通常使用I字母打头的大驼峰命名法
  • 变量一旦使用了接口类型之后,对对象的属性,属性类型 都有约束,属性不能多不能少,类型也不能错
  • 当一个对象类型被多次使用时,可以使用 type 来描述对象的
interface IPerson {
  name:string
  age:number
  sayHi(): void
}
​
let pi:IPerson ={
  name:"cp",
  age:30,
  sayHi() {}
}

6.1 interface vs type

interface(接口)和 type(类型别名)的对比

  • 相同点:都可以给对象指定类型

  • 不同点:

    • interface (接口) :只能为对象指定类型

    • type(类型别名)

      • 不仅可以为对象指定类型
      • 还可以为任意类型指定别名
  • 推荐:能使用type 就是用 type

    interface IPerson {
      name: string
      age: number
      sayHi(): void
    }
    ​
    // 为对象类型创建类型别名
    type IPerson = {
      name: string
      age: number
      sayHi(): void
    }
    ​
    // 为联合类型创建类型别名
    type NumStr = number | string
    

6.2 可选属性

单独对一些属性进行额外的可选处理

interface IPerson {
  name:string,
  age?:number
}
// age属性是可选的,可有可无
let pi:IPerson ={
  name:"cp"
}

6.3 只读属性 (readonly)

一些对象的属性不可以修改,只可以读,可以使用readonly 修饰

interface IPerson {
  readonly id:string,//只读属性
  name:string,
  age?:number
}
​
let p1:IPerson ={
  id:'1001',
  name:"cp"
}
​
p1.id="1002"

6.4 extends (继承接口)

接口也支持继承,让接口可以分割成可重用的模块

如果两个接口之间有相同的属性或方法,可以将公共的属性和方法抽离出来,通过继承来实现复用

interface Shape{
  color:string
}
interface Square extends Shape{
  sideLength:number
}
​
let s:Square={
  color:'blue',
  sideLength:100
}

🥭条件类型

如果 T 包含的类型是 U 包含的类型的"子集" ,哪么取 X 否则取结果 Y

//类似 三元表达式
T extends U ?X:Y

🍍 例子

type NonNullable<T> = T extends null | undefined ? never : T;
​
// 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。
let demo1: NonNullable<number>; // => number
let demo2: NonNullable<string>; // => string
let demo3: NonNullable<undefined | null>; // => never

🥭 infer

infer 最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量。

type ParamType<T> = T extends (param: infer P) => any ? P : T;

6.5 &(交叉类型)

使用交叉类型,来实现接口继承的功能

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

    //使用 type 自定义类型来模拟 Point2d
    type Point2D={
      x:number,
      y:number
    }
    // 使用交叉类型后,同时具有了 Point2D和后面的所有属性type Point3D=Point2D&{
      z:number
    }
    ​
    let o:Point3D={
      x:1,
      y:2,
      z:3
    }
    

6.6 &和extends的对比

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

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

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

    • extends(接口继承)

      interface A{
         fn :(value:number)=> string
      }
      interface B extends A{  // 报错
         fn:(value:string)=>string
      }
      
    • & 交叉类型

      interface A {
        fn:(value:number)=>string
      }
      interface B {
       fn:(value:string)=>string
      }
      ​
      type C=A&B
      
  • 以上代码,接口继承会报错(类型不兼容),交叉类型没有错误,可以简单的理解为:

    fn:(value:string|number)=>string
    

7.泛型 ( Generics )

定义一个函数或类时,无法确定其中使用的类型,此时泛型便能够发挥作用

  • 泛型:是可以在保证类型安全提,让函数等与多种类型一起工作,从而实现复用,常用于:函数 ,接口,class中
  • 在函数右侧声明占位类型 T是一般写法Type的写法,它代表一个类型变量而不是值 ,可以在调用函数的时候,传入具体的类型

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

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

    // 创建泛型函数
    function id<T>(value:T): T {
      return value
    }
    //使用泛型函数 
    //传入number类型
    const numberId= id<number>(1001)
    //传入string类型
    const stringID=id<string>("1002")
    ​
    // 简化:调用时可以省略类型,类型会由Ts自动推断出来 
    const ID=id(10)
    

7.1 泛型约束 (extends )

使用 extends 关键字来为 泛型函数添加类型约束

  • 传入的实参只要有length属性即可
  • 函数的参数为某一类型时,函数的返回值也为该类型

注意

  • 泛型添加约束在我们写代码时不会使用

  • 主要是使用别人封装的方法时会用到

    interface ILength {
      length:number
    }
    ​
    function getArrLen<T extends ILength > (value:T):T{
      // 这里的类型添加了约束,不再报错
      console.log(value.length)
      return value
    }
    ​
    // const res1 = fn<Number>(1) // 报错
    const res2 = fn('abc')
    const res3 = fn([1, 2, 3])
    const res4 = fn({
      length: 11
    })
    

7.2 多个变量

语法:只需要在 < > 括号中,使用逗号分隔类型即可<T,U>

  • 比如我们有一个函数接收两个成员的元组,然后返回成员类型对调的新元组

    function switchTuple<T,U>(tuple:[T,U]):[U,T]{
      return [tuple[1],tuple[0]]
    }
    const newTuple=switchTuple<number,string>([1,'1'])
    
  • 泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量的约束)

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

    
    // keyof 取Type对象中的键 组成 联合类型 所以 key只能取Type中已有的键 也就是 name|age
    function getProp<Type,Key extends keyof Type>(obj:Tyep,key:key)
    getProp({name:'jack',age:'age'},'age')
    

7.3 泛型接口

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

  • 在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口。
  • 使用泛型接口时,需要显式指定具体的类型
  • 场景

    //用户信息接口
    let user={
      code:200,
      msg:'个人信息',
      result:{
        name:'cp'
        age:30
      }
    }
    //用户列表接口
    let userList={
      code:200,
      msg:'个人信息列表'
      result:[
        {
           name:'cp'
           age:30
        },
        {
           name:'cp'
           age:30
        },
      ]
    }
    
  • 实现:思路找重复的地方,抽象成泛型

    //定义用户信息掊口
    interface User{
      name:string,
      msg:number
    }
    //定义泛型接口()
    interface ApiRes<T> {
      code:number,
      msg:string,
      result:T
    }
    ​
    //验证
    let user Apires<User>={
      code:200,
      msg:'个人信息',
      result:{
        name:'cp'
        age:30
      }
    }
    

7.4 泛型类

//类型通用使用泛型
class Queue<T> {
  private data:T[]=[]
  add(item:T){
    return this.data.push(item)
  }
  pop():T {
    return this.data.shift()
  }
}
​
const unmberQueue=new Queue<number>()
numberQueue.add(100)

8 泛型工具

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

  • 基于泛型实现的内置工具(泛型适用于多种类型,更加通用)

8.1 Partial

作用:Partial用来构造一个类型,将Type的所有属性设置为可选

type Props={
  id:string,
  children:number[]
}
//构造出来的新类型PartiaProps结构和Props相同,但是所有的属性都变为可选的
 type PartiaProps=Partial<Props>
  
 // let Obj:Props={id:'1'}//报错
  let obj2:PartiaProps={id:'1'}//ok

8.2 Readonly

作用:Readonly用来构造一个类型,将所有的属性设置为只读

type Props={
  id:string,
  children:number[]
}
 type PartiaProps=Readonly<Props>
  
 let Props:ReadonlyProps={id:'1',children:[]}
 // 错误演示
props.id='2' //因为使用Readonly设置为只读属性

8.3 Pick

作用:从已存的接口中选择一组属性来构造新类型(Pick<Type,Keys>)

  • pick 工具类型有两个类型变量:1 表示选择谁的属性,2表示选择几个属性

  • 第二个类型变量传入的属性只能是第一个类型变量中存在的属性

    interface Props {
      id:string,
      title:string,
      children:number[]
    }
    //第二个类型变量传入的属性只能是第一个类型变量中存在的属性
    type PickProps=Pick<Props,'id'|'title'>
    ​
    //构造出来的新类型pickProps,只有id和title两个属性类型
    let obj:PickProps={
      id:"1001",
      title:"this is title"
    }
    

8.4 Record

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

  • Record 工具类型有俩个类型变量,1. 表示对象有哪些属性,2. 表示对象value的类型

  • 些类型与索引签名的作用一致

    //构建的新对象类型RecordOb表示:这个对象有三个属性分别为a/b/c,属性值的类型都是string[]
    type RecordObj=Record<'a'|'b'|'c',string[]>
    let obj:RecordObj={
      a:['1'],
      b:['2'],
      c:['3']
    }
    

8.5 Exclude

用于过滤不需要的类型

//通过从所有可分配给的联合成员中排除来构造一个 Exclude 类型type T0 = Exclude<"a" | "b" | "c", "a">;
type T0 = "b" | "c"
'
'
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
type T1 = "c"

9 索引签名类型

9.1对象

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

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

  • 使用【key:string】约束该接口中允许出现的属性名称。表示只要是 string类型的属性名称,都可以出现对象中

  • 前置知识:JS中对象({})的键是string类型的

    interface AnyObject{
     //key 只是一个占位符,可以换成任意合法的变量名称
      [key:string]:number
    }
    let obj:AnyObject={
      a:1,
      b:2
    }
   

interface AnyObject{ //可以限制后缀为 `x` [key:`${string}x`]:number }

let obj:AnyObject={ ax:1, bx:2 }

9.2 数组

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

  • 该索引签名类型表示:只要是number类型的键(索引)都可以出现在数组中,或者说数组中可以有多个任意元素

  • 同时也符合数组索引是number类型这一前提

        interface MyArray<T> {
           [n:number]:T
        }
        let arr:MyArray<number>=[1,3,5]
    
        arr[0]
    

10 映射类型

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

映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]

  • key in PropKeys 表示 key 可以是 PropKeys 联合类型中的任意一个,类似于 fonin (let k in obj)

  • 比如,类型PropKeys 有x/y/z,另一个类型Type1 x/y/z ,并且type1 中的x/y/z的类型相同

    type PropKeys ="x"|"y"|"z"
    type Type1={x:number;y:number;z:number}
    ​
    //简化上述写法
    type PropKeys ="x"|"y"|"z"
    type Type2={
      [key in PropKeys ]:number
    }
    

10.1 in(联合类型创建)

  • in 用于取联合类型的值。主要用于数组和对象的构造

  • 注意:映射类型只能在类型别名中使用,不能在接口使用

    type name = 'firstName' | 'lastName';
    type TName = {
      [key in name]: string;
    };
    ​
    //使用in 相当于
    type TName={
      firstName:string
      lastName:string
    }
    

10.2 typeof (获取类型)

使用场景:在类型上下文中获取变量或属性的类型,来简化类型书写

  • 通过 typeof 操作符获取 p 变量类型 并赋值给 point 类型变量

    let p={x:1,y:2}
    function formatPoint(point:{x:number;y:number}){}
    formatPoint(p)
    ​
    //此处使用 typeof 获取p变量类型 在赋值给 point
    function formatPoint2(point:typeof p){}
    formatPoint2({x:1,y:2})
    
  • 注意:typeof只能用来查询变量属性的类型,无法查询其他形式的类型(比如,函数调用的类型)

    let P={x:1,y:2}
    let num:typeof p.x //num:string//不允许用来查询函数返回类型
    function add(num1:number,num2:number){
      return unm1+unm2
    }
    ​
    let ret:typeof add(2,2) // 报错
    

10.3 keyof (对象类型创建)

根据对象类型创建

  • 首先,执行keyof Props获取到对象类型 Props中所有键的联合类型,形成 ‘a'|'b'|'c'
  • 然后 key in ..... 就表示 可以是Props中所有的键名称中的任意一个
type Props={a:number;b:string;c:boolean}
type Type3={[key in Keyof Props ]:number} //  key in .....  就表示 可以是Props中所有的键名称中的任意一个
  • keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型

    //`keyof`与Object.keys略有相似,只是`keyof`是取 `interface` 的键后会保存为联合类型
    interface iUserInfo {
      name: string;
      age: number;
    }
    type keys = keyof iUserInfo;
    

    Snipaste_2022-05-26_09-46-16.png

  • 实现一个函数getValue取得对象的 Value。

    在未接触Keyof

    function getValue(o: object, key: string) {
      return o[key];
    }
    const obj1 = { name: '张三', age: 18 };
    const name = getValue(obj1, 'name');
    ​
    

    使用keyofj时

    function getValue<T extends Object, K extends keyof T>(o: T, key: K): T[K] {
      return o[key];
    }
    ​
    const obj1 = { name: '张三', age: 18 };
    const a = getValue(obj1, 'hh');
    ​
    

10.4 typeof与keyof 一起使用

function getAttri (obj:object,key:string){
 // 如果 obj[key] 这种不明确obj有没有key这个类型,所以会报错
  return obj[key as keyof typeof obj]
}
const a={name:'hao',age:28}
getAttri(hd,'name')

10.5 索引查询类型

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

  • Props['a']表示查询类型Props中属性 ’a‘对应的类型 number,所以 TypeA的类型为number

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

    type Props={a:number; b:string; c:boolean}
    type TypeA=Props['a']  //type TypeA=number
    
  • 同时查询多个索引的类型

    type Props={a:number;b:string,c:boolean}
    // 使用字符串字面量的联合类型,获取属性 a 和 b 对应的类型,结果为:string|number 
    type TypeA=Props['a':'b']//string|number
    //使用keyof操作符 获取 Props中所有键对应的类型,结果为 string| number |boolean
    type TypeA=Props[keyof Props] //string| number |boolean
    

11 declare

declare 是用于声明形式存在的

 `declare var/let/const`用来声明全局的变量。

`declare function` 用来声明全局方法(函数)

`declare class` 用来声明全局类

`declare namespace` 用来声明命名空间

`declare module` 用来声明模块

12 类型声明文件

12.1 ts中的两种声明类型文件

.ts 文件

  1. 包含类型信息可执行代码
  2. 可以被编译 .js文件,然后,执行代码
  3. 用途:编写程序代码的地方

.d.ts 文件

  1. 只包含类型信息的类型声明文件
  2. 不会生成js文件,仅用于提供类型信息
  3. 用途:为JS提供类型信息

总结

  1. .ts(代码实现文件)
  2. .d.ts是(类型声明文件)
  3. 如果要为 JS 库提供类型信息,要使用 .d.ts 文件

12.2 内置类型声明文件

  • TS为 JS 运行时可用的所有标准化内置Api都提供了声明文件

  • 比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息

    const strs = ['a', 'b', 'c']
    // 鼠标放在 forEach 上查看类型
    strs.forEach
    
  • 实际上这都是 TS 提供的内置类型声明文件

  • 可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容

  • 比如,查看 forEach 方法的类型声明,在 VSCode 中会自动跳转到 lib.es5.d.ts 类型声明文件中

  • 当然,像 window、document 等 BOM、DOM API 也都有相应的类型声明(lib.dom.d.ts)

12.3 第三方库类型声明文件

🥭说明

  • 目前,几乎所有常用的第三方库都有相应的类型声明文件

  • 第三方库的类型声明文件有两种存在形式:

    1. 库自带类型声明文件
    2. 由 DefinitelyTyped 提供。

🍋 库自带类型声明文件

  • 比如:axios

  • 查看 node_modules/axios 目录

  • 过程:正常导入该库时,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。

🍎由 DefinitelyTyped 提供

  • DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明

  • DefinitelyTyped 链接

  • 可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*

  • 比如,@types/node、@types/jquery等

  • 在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示

    Snipaste_2022-06-05_15-17-48.png

解释:当安装@types/*类型声明包后,TS也会自动加载该类声明包,以提供该库的类型声明。

补充:TS官方文档提供了一个页面,可以来查询@types/*库。

12.4 自定义类型声明文件

说明

  • 自定义类型声明文件有两个作用:

    • 类型共享
    • 为已有 JS 文件提供类型声明

类型共享

  • 作用:

    • 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
  • 操作步骤:

    1. 创建 index.d.ts 类型声明文件。
    2. 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
    3. 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。

为已有 JS 文件提供类型声明

  • 作用:

    • 在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。
    • 成为库作者,创建库给其他人使用。
  • 演示:基于最新的 ESModule(import/export)来为已有 .js 文件,创建类型声明文件。

  • 补充说明

    • TS 项目中也可以使用 .js 文件。

    • 在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。

    • declare 关键字:

      • 用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
      • 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
      • 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。
  • 原始文件

    创建 test.ts

        let count = 10
        let songName = '痴心绝对'
        let position = {
          x: 0,
          y: 0
        }
        ​
        function add(x, y) {
          return x + y
        }
        ​
        function changeDirection(direction) {
          console.log(direction)
        }
        ​
        const fomartPoint = point => {
          console.log('当前坐标:', point)
        }
        ​
        export { count, songName, position, add, changeDirection, fomartPoint }
    
  • 定义类型声明文件

    创建 src/type/test.d.ts

    //声明模块(被匹配的文件都属于该模块)
    declare module '*/test.js'{
    
        export let count:numberexport let songName: stringinterface Position {
          x: number,
          y: number
        }
        ​
        export let position: Positionexport function add (x :number, y: number) : numbertype Direction = 'left' | 'right' | 'top' | 'bottom'export function changeDirection (direction: Direction): voidtype FomartPoint = (point: Position) => voidexport const fomartPoint: FomartPoint//声明class接口
        interface Tests{
            name:string
            //new()代表构造函数
            new():Tests
        }
        //声明class类型
        export const Test:Tests;
    }
    
  • 引入 text.d.ts 自定义类型声明文件

    tsconfig.json 文件引入

    
        {
          "extends": "@vue/tsconfig/tsconfig.web.json",
          "include": ["env.d.ts", "src/**/*", "src/**/*.vue",  "src/type/test.d.ts"],
          "compilerOptions": {
            "baseUrl": ".",
            "paths": {
              "@/*": ["./src/*"]
            },
            "suppressImplicitAnyIndexErrors": true
          },"references": [
            {
              "path": "./tsconfig.vite-config.json"
            }
          ]
        }

12.5 lodash- 配合 TS 使用

在ts 中使用 lodash 会报错需要安装 ts 声明 文件库

  • 安装 npm install lodash -s
  • 安装 声明文件库 npm install @types/lodash -D

12.6 Axios - 配合 ts 使用

目标:掌握 axios 配合 ts 使用的方式 频道接口:geek.itheima.net/v1_0/channe…

复习 axios 的使用

import axios from 'axios'
async function getData () {
    const res = await axios.get('http://geek.itheima.net/v1_0/channels')
    console.log(res.data.data.channels)
}
getData()

配合 ts 使用

  • 当 axios 结合 ts 使用时,需要给返回的数据添加数据类型

    import axios from 'axios'
    type Channel = {
        id: number
        name: string
    }
    type ChannelRes = {
        data: {
            channels: Channel[]
        },
        message: string
    }
    async function getData () {
        const res = await axios.get<ChannelRes>('http://geek.itheima.net/v1_0/channels')
        console.log(res.data.data.channels)
    }
    getData()
    
  • 保存数据,将数据渲染到页面上

    <script setup lang="ts">
        import { ref } from 'vue'
    import axios from 'axios'
    type Channel = {
        id: number
        name: string
    }
    type ChannelRes = {
        data: {
            channels: Channel[]
        },
        message: string
    }
    let list = ref<Channel[]>([])
    async function getData () {
        const res = await axios.get<ChannelRes>('http://geek.itheima.net/v1_0/channels')
        list.value = res.data.data.channels
    }
    getData()    
    </script>
    <template>
        <ul>
            <li v-for="item in list" :key="item.id">{{ item.name }}</li>
        </ul>
    </template>
    

注意

  • axios.get() / axios.post() / axios.put() ... 可以直接通过泛型来设置返回数据的类型
  • axios()无法直接通过泛型设置返回数据类型

13 使用TS扩展

可以通过扩展 RouteMeta 接口来输入 meta字段

import 'vue-router'declare module 'vue-router' {
  interface RouteMeta {
    //是否可选的
    title?: string
   //每个路由都必须声明
   requiresAuth:boolean
  }
}

技巧:可以通过Ctr+鼠标左键 (Mac;option+鼠标左键)来查看具体的类型信息。

14 d.ts 实现类型共享

  • 创建需要共享的类型,并使用export 导出(TS中的类型也可以使用 import/实现模块化功能)

  • 在需要使用共享类型的 .ts文件中,通过import 导入即可(.d.ts 后缀导入时,直接省略)

    创建 index.d.ts

    //商品信息
    export interface Good{
      id:string,
      name:string,
      price:number
    }
    

    引用

    import type {Good} from './data'
        
    type ResGood={
      code:string,
      items:Good[]
    }
    

🍊 接到新项目,需要用 vue3 让我瞅瞅有什么新特性?

🍉 基于 pinia 封装持久化插件(ts版)

🍋 TS+vue3 如何结合打套组合拳