Vue3 知识总结【TypeScript】

299 阅读17分钟

TypeScript

基本概念

是什么

TypeScript 简称:TS,是 JavaScript 的超集,简单来说就是:JS 有的 TS 都有(在 JS 基础之上,为 JS 添加了类型支持

官方文档:TS官网

为什么

内容:

  • 背景:JS 的类型系统存在“先天缺陷”弱类型,JS 代码中绝大部分错误都是类型错误(Uncaught TypeError)

    • 开发的时候,定义的变量本应该就有类型
  • 这些经常出现的错误,导致了在使用 JS 进行项目开发时,增加了找 Bug、改 Bug 的时间,严重影响开发效率

  • 从编程语言的动静来区分,TypeScript 属于静态类型的编程语言JavaScript 属于动态类型的编程语言

    • 静态类型:编译期做类型检查
    • 动态类型:执行期做类型检查
  • 代码编译和代码执行的顺序:1 编译 2 执行

  • 对于 JS 来说:需要等到代码真正去执行的时候才能发现错误(晚)

  • 对于 TS 来说:在代码编译的时候(代码执行前)就可以发现错误(早)

并且,配合 VSCode 等开发工具,TS 可以提前到在编写代码的同时就发现代码中的错误,减少找 Bug、改 Bug 时间

对比:

  • 使用 JS:

    1. 在 VSCode 里面写代码
    2. 在浏览器中运行代码 --> 运行时,才会发现错误【晚】
  • 使用 TS:

    1. 在 VSCode 里面写代码 --> 写代码的同时,就会发现错误【早】
    2. 在浏览器中运行代码

怎么用

  1. 全局安装

    npm i -g typescript 
    
    # Or
    
    yarn global add typescript
    
    • typescript 包:用来编译 TS 代码的包,提供了 tsc 命令,实现了 TS -> JS 的转化
    • 注意:Mac 电脑安装全局包时,需要添加 sudo 获取权限:sudo npm i -g typescript yarn 全局安装:sudo yarn global add typescript
  2. 编译并运行

    • 编译

      let a: number = 10
      console.log(a);
      

      在终端输入命令:

      ts .\文件名
      

      编译成功后会生成一个同名的 js 文件

    • 运行

      node .\编译后的js文件名
      

创建 vue 项目

yarn create vite 
文件名:vite-ts-demo  
类型选择 vue-ts

基础

类型注解

  • TypeScript 是 JS 的超集,TS 提供了 JS 的所有功能,并且额外的增加了:类型系统

    • 所有的 JS 代码都是 TS 代码
    • JS 有类型(比如,number/string 等),但是 JS 不会检查变量的类型是否发生变化,而 TS 会检查
  • TypeScript 类型系统的主要优势:可以显示标记出代码中的意外行为,从而降低了发生错误的可能性

let a: number = 10
console.log(a);
  • 说明:代码中的 : number 就是类型注解

  • 作用:为变量添加类型约束。比如,上述代码中,约定变量 age 的类型为 number 类型

  • 解释:约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错

  • 约定了类型之后,代码的提示就会非常的清晰

  • 错误演示:

    // 错误代码:
    // 错误原因:将 string 类型的值赋值给了 number 类型的变量,类型不一致
    let age: number = '18'
    

类型概述

  • JS 已有类型

    • 原始类型,简单类型(number/string/boolean/null/undefined
    • 复杂数据类型(数组,对象,函数等)
  • TS 新增类型

    • 联合类型
    • 自定义类型(类型别名)
    • 接口
    • 元组
    • 字面量类型
    • 枚举
    • void

原始数据类型

  • 原始类型:number/string/boolean/null/undefined
  • 特点:简单,这些类型,完全按照 JS 中类型的名称来书写
let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false// 等等...

数组类型

内容:

  1. 写法一(推荐)

    const arr: number[] = [1, 2]
    const arr1: string[] = ['1', '2']
    
  2. 写法二(了解)

    const arr2: Array<number> = [1, 2]
    const arr3: Array<string> = ['1', '2']
    

联合类型

能够通过联合类型将多个类型组合成一个类型

  1. 混合型联合类型

    const arr4: (string | number)[] = [1, '2']
    
  2. 纯联合类型

    const arr5: string[] | number[] = ['chao', 'dao']
    const arr6: string[] | number[] = [1, 2]
    

总结:|(竖线)在 TS 中叫做联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种

注意:

这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(|| 或)混淆了

类型别名

  • 类型别名(自定义类型):为任意类型起别名
  • 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用
type myType = (string | number)[]
const arr4: myType = [1, '2']

总结:

  1. 使用 type 关键字来创建自定义类型
  2. 类型别名(比如,此处的 CustomArray)可以是任意合法的变量名称
  3. 推荐使用大写字母开头
  4. 创建类型别名后,直接使用该类型别名作为变量的类型注解即可

函数类型

能够给函数指定类型

基本使用

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

  • 为函数指定类型的两种方式:

    1. 单独指定参数、返回值的类型

      function chao(name: string, age: number) {
        console.log(name, age);
      }
      
      chao('超', 21)
      
      const tie = (num: number) => {
        return num
      }
      
      tie(21).toFixed(2)
      
    2. 同时指定参数、返回值的类型

      type myAdd = (a: number, b: number) => number
      const add: myAdd = (a, b) => {
        return a + b
      }
      
      console.log(add(1, 2));
      

注意:

  1. 调用函数时,传递的 实参个数 和 实参类型,都要和形参设置的一致。
  2. 使用箭头函数时,如果形参只有一个且想要设置类型,括号不能省略!

void 类型

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

function greet(name: string): void {
  console.log('Hello', name)
}

注意:

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

// 如果什么都不写,此时,add 函数的返回值类型为: void
const add = () => {}
// 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
const add = (): void => {}

但,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以。

const add = (): undefined => {
  // 此处,返回的 undefined 是 JS 中的一个值
  return undefined
}

可选参数

使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就用到可选参数了。

const chose = (start?: number, end?: number): void => {
  console.log(`start:${start},end:${end}`);
}

chose()
chose(1)
chose(1,3)

注意:

可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数。

类型推论

在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型

换句话说:由于类型推论的存在,这些地方,类型注解可以省略不写

发生类型推论的 2 种常见场景:

  1. 声明变量并初始化时
  2. 决定函数返回值时

总结:

  1. 能省略类型注解的地方就省略偷懒,充分利用TS类型推论的能力,提升开发效率)
  2. 如果不知道类型,可以通过鼠标悬停在变量名称上,利用 VSCode 的提示来查看类型

变量声明

image.png

参数默认值

可以为参数设置默认值,设置默认值后TS会自动帮你判断参数的数据类型,此时传递其他类型会报错。

image.png

对象类型

JS 中的对象是由属性和方法构成的,而 TS 对象的类型就是在描述对象的结构(有什么类型的属性和方法)

对象类型的写法:

  • 空对象

    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() {}
    }
    

总结:

  1. 使用 {} 来描述对象结构
  2. 属性采用 属性名: 类型 的形式
  3. 方法采用 方法名(): 返回值类型 的形式

使用类型别名

type ObjType = {
  name: string;
  age: number;
  address(str: string): void;
}

let obj: ObjType = {
  name: 'chao',
  age: 20,
  address(str) {
    console.log(`我住在${str}`);
  }
}

obj.address('吉山幼儿园')

箭头函数形式的方法类型

type ObjType = {
  address(str: string): void;
  sayHi: (a: number, b: number) => number;
}

let obj: ObjType = {
  address(str) {
    console.log(`我住在${str}`);
  },
  sayHi: function(a, b) {
    return a + b
  }
}

obj.address('吉山幼儿园')
console.log(obj.sayHi(22, 3878));

对象可选属性

对象也可以设置可选属性,同样设置一个 ? 即可。

案例需求:模拟 axiosmethod 为可选状态。

type AxiosType = {
  url: string;
  method?: string;
}

const axios = (config: AxiosType) => {}

axios({
  url: 'xxx'
})

axios({
  url: 'xxx',
  method: 'post'
})

接口类型

基本使用

当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的

注意:

  1. 使用 interface 关键字来声明接口
  2. 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以 I 开头
  3. 声明接口后,直接使用接口名称作为变量的类型
  4. 因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)
interface IPerson {
  name: string
  age: number
  sayHi(): void
}

let person: IPerson = {
  name: 'jack',
  age: 19,
  sayHi() {}
}

interface vs type

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

  • 相同点:都可以给对象指定类型
  • 不同点:
    1. 接口,只能为对象指定类型
    2. 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名

推荐:

能使用 type 就是用 type,实际工作中根据公司规定来使用

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

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

// 为联合类型创建类型别名
type NumStr = number | string

接口继承

在之前遇到两个类型一样时只能单独声明,代码如下所示。

interface IPoint2D {
  x: number
  y: number
}

interface IPoint3D {
  x: number
  y: number
  z: number
}

const D2: IPoint2D = {
  x: 11,
  y: 22
}

const D3: IPoint3D = {
  x: 11,
  y: 22,
  z: 33
}

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

interface IPoint3D extends IPoint2D {
  z: number
}

效果依旧能实现。

有些公司就喜欢用 type ,那么 type 如何实现继承呢?可以通过 & 合并符合并一下。

type IPoint3D = IPoint2D & { z: number }

总结:

通过 extends 关键字实现继承效果, extends 只对 interface 生效。

元组类型

场景:在地图中,使用经纬度坐标来标记位置信息

可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型

let position: number[] = [116.2317, 39.5427]
position[3] = 33.33
  • 使用 number[] 的缺点:不严谨,因为该类型的数组中可以出现任意多个数字
  • 更好的方式:元组 Tuple
  • 元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型
let position: [number, number] = [39.5427, 116.2317]
position[3] = 33.33 // 报错,不能赋值给undefined
position = [1, 2]

image.png

总结:

  1. 在ts中,元组是特殊的数组。
  2. 元组的长度和类型是固定的。
  3. 元组可以修改数据,但是长度和类型要保持一致。

字面量类型

基本使用

let str1 = 'hello'
const str2 = 'hello'

以上两行代码,str1 是一个变量(let),它的值可以是任意字符串,所以类型为 :stringstr2 是一个常量(const),它的值不能变化只能是 'hello' ,所以,它的类型为 :'hello'

注意:

此处的 'hello',就是一个字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型。任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用。

使用场景

  • 使用模式:字面量类型配合联合类型一起使用
  • 使用场景:用来表示一组明确的可选值列表

案例需求:修改前面的 axios 方法,让其 method 只能使用特定的方法。

image.png

此时,参数 method 的值只能是 get/post/put/delete/patch 中的任意一个。

优势:

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

枚举类型

基本使用

  • 枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值
  • 枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
// 创建枚举
enum Direction { Up, Down, Left, Right }

// 使用枚举类型
function changeDirection(direction: Direction) {
  console.log(direction)
}

// 调用函数时,需要应该传入:枚举 Direction 成员的任意一个
// 类似于 JS 中的对象,直接通过 点(.)语法 访问枚举的成员
changeDirection(Direction.Up)

总结:

  1. 使用 enum 关键字定义枚举
  2. 约定枚举名称以大写字母开头
  3. 枚举中的多个值之间通过 ,(逗号)分隔
  4. 定义好枚举后,直接使用枚举名称作为类型注解

数字枚举

问题:我们把枚举成员作为了函数的实参,它的值是什么呢?

  • 通过将鼠标移入 Direction.Up,可以看到枚举成员 Up 的值为 0

注意:

枚举成员是有值的,默认为:从 0 开始自增的数值

  • 我们把,枚举成员的值为数字的枚举,称为:数字枚举
  • 当然,也可以给枚举中的成员初始化值
// Down -> 11、Left -> 12、Right -> 13
enum Direction { Up = 10, Down, Left, Right }

enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }

字符串枚举

字符串枚举:枚举成员的值是字符串

注意:

字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}

枚举实现原理

  • 枚举是 TS 为数不多的非 JavaScript 类型级扩展(不仅仅是类型)的特性之一
  • 因为:其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)
  • 也就是说,其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为 JS 代码
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}

// 会被编译为以下 JS 代码:
var Direction;

(function (Direction) {
  Direction['Up'] = 'UP'
  Direction['Down'] = 'DOWN'
  Direction['Left'] = 'LEFT'
  Direction['Right'] = 'RIGHT'
})(Direction || Direction = {})

总结:

  1. 枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表。
  2. 一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效。

any 类型(不推荐)

原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)

因为当值的类型为 any 时,可以对该值进行任意操作,并且不会有代码提示

let obj: any = { x: 0 }

obj.bar = 100
obj()
const n: number = obj

以上操作都不会有任何类型错误提示,即使可能存在错误

总结:

  • 尽可能的避免使用 any 类型,除非临时使用 any 来“避免”书写很长、很复杂的类型

  • 其他隐式具有 any 类型的情况

    1. 声明变量不提供类型也不提供默认值
    2. 函数参数不加类型

注意:

因为不推荐使用 any,所以,这两种情况下都应该提供类型


类型断言

这个类型太宽泛(不具体) ,无法操作 href 等 a 标签特有的属性或方法,这种情况下就需要使用类型断言指定更加具体的类型

image.png

总结:

  1. 使用 as 关键字实现类型断言
  2. 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElementHTMLElement 的子类型)
  3. 通过类型断言,类型变得更加具体,这样就可以访问 input 标签特有的属性或方法了

泛型

基本介绍

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

没有泛型的情况,情景模拟:封装一个函数,这个函数能够返回数值型或字符串型数据

image.png

可以发现,虽然能够使用数值型或字符串型的数据,但是能使用的方法只有几个,这是为什么呢?

因为 TS 不会执行代码,只会检查代码,我们给了他两种格式的数据,他不知道最终会返回哪一个数据,因此只挑选数值型与字符串型都拥有的方法。

总结:

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

泛型函数

由上可知,我们需要定义一个泛型来解决这种问题。

const fn = <T>(value: T): T => {
  return value
}
fn(1).toFixed

总结:

  1. 语法:在函数名称的后面添加 <>(尖括号),尖括号中添加类型变量,比如此处的 Type。
  2. 类型变量 Type,是一种特殊类型的变量,它处理类型而不是值
  3. 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)。
  4. 因为 Type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型。
  5. 类型变量 Type,可以是任意合法的变量名称。

泛型调用

fn<number>(1,2,3,4)

简写方式:

fn(1,2,3,4)

总结:

  1. 在调用泛型函数时,可以省略 <类型> 来简化泛型函数的调用
  2. TS 内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量 Type 的类型。如,传入实参 10,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型。
  3. 推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读。

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

接口类型泛型

下面有一个实际场景,接口返回的参数有很多种情况:

  1. 成功

    const res1 = {
      code: 200,
      msg: 'success',
      data: ['d', 'dd']
    }
    
  2. 失败

    const res2 = {
      code: 404,
      msg: 'error',
      data: null
    }
    
  3. 数据不一致

    const res3 = {
      code: 200,
      msg: 'success',
      data: [1,2,3]
    }
    

此时,我们需要为这些对象统一声明一个接口类型,前两个数据可以统一设置,最后一个数据该如何设置呢?

我们用到泛型配合接口类型来使用。

interface res<T> {
  code: number
  msg: string
  data: T
}

const res3: res<number[]> = {
  code: 200,
  msg: 'success',
  data: [1,2,3]
}

对象结构添加泛型

const res1:res<love[]> = {
  code: 200,
  msg: 'success',
  data: [{
    id: 1, content: 'dao'
  }]
}

见上方代码,属性data是一个数组对象,如何定义数组对象内每一个属性的类型呢?可以使用泛型接口来定义数据类型。

interface res<T> {
  code: number
  msg: string
  data: T
}

interface love  {
  id: number;
  content: string;
}

const res1:res<love[]> = {
  code: 200,
  msg: 'success',
  data: [{
    id: 1, content: 'chao'
  }]
}

此时会有相应的约束和代码提示。

获取对象键名

通过关键字 keyof 可以获取对象的键名,后续代码书写时都会有相应的提示。

const get = <T extends object, K extends keyof T>(obj:T, key:K) => {
  return obj[key]
}

console.log(get({name:'chao',demo:'tie',miss:'ze'},'name'));

添加约束

const fn = <T>(params: T): number => {
  return params.length
}

console.log(fn({length: 4}));

如上方使用场景,我们需要参数内传递一个属性 length 来使用,但使用时可能没有传递,此时我们就无法使用,返回 undefined 。因此要为泛型添加一个约束,约束使用者必须传递 length 属性。

代码如下所示:

const fn = <T extends {length: number}>(params: T): number => {
  return params.length
}

console.log(fn({length: 4}));

此时使用时会强制约束必须传数值型的 length 属性。

泛型拓展

  1. 允许多个泛型变量,用逗号隔开
  2. 可以为泛型设置默认值
  3. unKnown 表示未知类型,常用于占位

kuozhan.png