likes
comments
collection
share

浅尝Typescript(二)

作者站长头像
站长
· 阅读数 60

浅尝Typescript(二)

01.泛型-问题导入

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

//js版本
function id(value){ return value }

如何使用ts来定义这个函数?

//ts版本
function id(value: number): number { return value }

比如,id(10) 调用以上函数就会直接返回 10 本身。但是,该函数只接收数值类型,无法用于其他类型

为了能让函数能够接受任意类型,可以将参数类型修改为 any。但是,这样就失去了 TS 的类型保护,类型不安全

function id(value: any): any { return value }

02泛型-泛型函数

理解泛型

「泛型」,顾名思义,宽泛的类型,就是类型是不固定的,不写死的; 不是任意类型

function f1(a: number) {}
function f2(a: string) {}

可以适用于多个类型,使用类型变量(比如T)帮助我们捕获传入的类型,之后我们就可以继续使用这个类型。

本质是参数化类型,通俗的讲,就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和函数的创建中,分别成为泛型类泛型接口泛型函数

定义格式

function 函数名<类型变量1,类型变量2,...>(参数1:类型1,参数2:类型2,...): 返回值类型 {
  
}

在函数名称的后面写 <>(尖括号),尖括号中添加类型变量

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

示例

// T 只是一个名字而已,可以改成其他的,例如 R

function fn<T>(value: T): T { return value }
  1. 类型变量 T,是一种特殊类型的变量,它处理类型而不是值
  2. 因为 T是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
  3. 类型变量 T,可以是任意合法的变量名称

调用格式

const 返回值  = 泛型函数名<类型1,类型2,...>(实参1,实参2,实参3,......)

语法:在函数名称的后面添加 <>(尖括号),尖括号中指定具体的类型

示例

const num = fn<number>(10)
const str = fn<string>('a')
  1. 当传入类型 number 后,这个类型就会传递给函数声明时对应的类型变量
  2. 通过泛型就做到了让 fn 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全

03简化泛型函数调用

// 省略 <number> 调用函数
let num = id(10)
let str = id('a')
  • 解释:

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

  • 推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
  • 说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数

04 泛型约束

  • 默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性
  • 比如,id('a') 调用函数时获取参数的长度:
function id<Type>(value: Type): Type {
  console.log(value.length)
  return value
}

id('a')
  • 解释:Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length
  • 此时,就需要为泛型添加约束来收缩类型 (缩窄类型取值范围)
  • 添加泛型约束收缩类型,主要有以下两种方式:1 指定更加具体的类型  2 添加约束

指定更加具体的类型

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

function id<Type>(value: Type[]): Type[] {
  console.log(value.length)
  return value
}

添加约束

// 创建一个接口
interface ILength { length: number }

// Type extends ILength 添加泛型约束
// 解释:表示传入的 类型 必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
function id<Type extends ILength>(value: Type): Type {
  console.log(value.length)
  return value
}
  • 解释:

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

  • 注意:传入的实参(比如,数组)只要有 length 属性即可(类型兼容性)

05多个类型变量

泛型的类型变量:

  • 可以有多个
  • 并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束) 比如,创建一个函数来获取对象中属性的值:
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')
  • 解释:

    a. 添加了第二个类型变量 Key,两个类型变量之间使用 , 逗号分隔。 b. keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型。 c. 本示例中 keyof Type 实际上获取的是 person 对象所有键的联合类型,也就是:'name' | 'age' d. 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性

// Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
// 如果要用到 对象 类型,应该用 object ,而不是 Object
function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key]
}

07.泛型接口

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

定义

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

使用

let obj: IdFunc<number> = {
  id(value) { return value },
  ids() { return [1, 3, 5] }
}
  • 解释:

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

拓展

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

const strs = ['a', 'b', 'c']
// 鼠标放在 forEach 上查看类型
strs.forEach

const nums = [1, 3, 5]
// 鼠标放在 forEach 上查看类型
nums.forEach
  • 解释:当我们在使用数组时,TS 会根据数组的不同类型,来自动将类型变量设置为相应的类型
  • 技巧:可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看具体的类型信息

08.使用vite来创建vue3+TS的项目

yarn create vite vite-ts-demo --template vue-ts

参考链接:vuejs.org/guide/types…

vue3配合ts中,还需要额外安装一个vscode插件:Typescript Vue Plugin

浅尝Typescript(二)

09 ts下使用props

之前 父传子,把props传递给子组件内部使用。可以通过defineProps来接收 defineProps配合vue默认语法进行类型校验(运行时声明)

<script setup>
// 运行时声明
const props = defineProps({
  money: {
    type: Number,
    required: true
  },
  car: {
    type: String,
    default: '小黄车'
  }
})
</script>

配合TS使用

  1. 类型校验

defineProps配合ts的泛型定义props,这样更直接

// 使用ts的泛型指令props类型
const props = defineProps<{
  money: number
  car?: string
}>()
  1. props可以通过解构来指定默认值
<script lang="ts" setup>
// 使用ts的泛型指令props类型
const { money, car = '小黄车' } = defineProps<{
  money: number
  car?: string
}>()
</script>

注意:

如果提供的默认值需要在模板中渲染,需要额外添加配置

vuejs.org/guide/extra…

// vite.config.js
export default {
  plugins: [
    vue({
      reactivityTransform: true
    })
  ]
}

10.ts下使用emits

  1. defineEmits配合运行时声明
<script lang="ts" setup>
const emit = defineEmits(['change', 'update'])
</script>
  1. defineEmits配合ts 类型声明,可以实现更细粒度的校验
const emit = defineEmits<{
  (e: 'changeMoney', money: number): void
  (e: 'changeCar', car: string): void
}>()

浅尝Typescript(二)

11.ts下使用ref设置初始值

目标

掌握ref配合ts如何使用

要点

  1. 通过泛型指定value的值类型,如果是简单值,该类型可以省略
  2. 如果是复杂类型,推荐指定泛型

示例

// 1. 通过泛型指定value的值类型,如果是简单值,该类型可以省略
const money = ref<number>(10)

const money = ref(10)

// 2. 复杂类型,推荐指定泛型
const list = ref([])

12.给复杂数据提前指定类型

答:为了更好的获得代码提示

情况1:如果复杂数据类型是可以被类型推断的,可以不用指定

// 2. 复杂类型,推荐指定泛型
const list = ref([{ id: 1, name: '吃饭', done: false },
{ id: 2, name: '睡觉', done: true }])

// template
// 这里的item.之后会自动有提示
<li v-for="item in list" :key="item.id"> {{item.name}} </li>

情况2:实际开发时,一般先给初始值为空数组

// 2. 复杂类型,推荐指定泛型
const list = ref([])

setTimeout(() => {
  list.value = [
    { id: 1, name: '吃饭', done: false },
    { id: 2, name: '睡觉', done: true }
  ]
})

// template
// 这里的item.之后没有提示
<li v-for="item in list" :key="item.id"> {{item.name}} </li>

改进

type Todo = {
  id: number
  name: string
  done: boolean
}
const list = ref<Todo[]>([])

13.ts下使用computed

目标

掌握computed配合typescript如何使用

内容

通过泛型可以指定computed计算属性的类型,通常可以省略

const leftCount = computed<number>(() => {
  return list.value.filter((item) => item.done).length
})
console.log(leftCount.value)

14.ts下的事件处理

目标

掌握事件处理函数配合typescript如何使用

基本示例 不写任何类型,会报错,但是可以跑起来

浅尝Typescript(二)

两种做法

  1. 在template中,明确传入指定$event
  2. 在回调函数中指定参数的类型
const move = (e: MouseEvent) => {
  mouse.value.x = e.pageX
  mouse.value.y = e.pageY
}

<h1 @mousemove="move($event)">根组件</h1>

15.ts下使用Template Ref

问题

<script setup lang="ts">
  const h1Ref = ref(null)
  const getElement = () => {
    console.log(h1Ref.value.innerHTML) // 这里会报错
  }
</script>

<tempalte>
  <div><h1 ref="h1Ref">在获取dom时通过泛型指定类型</h1>
</template>

上面的代码中,第4行会报错: 它认为h1Ref.value上没有innerHTML这个属性

ref用于获取页面中元素时,有两个状态:

  1. 初始转态 Null
  2. 绑定后的状态

改进

const imgRef = ref<HTMLImageElement | null>(null)

onMounted(() => {
  console.log(imgRef.value?.src)
})

技巧:如何查看一个DOM对象的类型:通过控制台进行查看

document.createElement('img').__proto__

16. TypeScript类型声明文件

背景

今天几乎所有的 JavaScript 应用都会引入许多第三方库来完成任务需求。 这些第三方库不管是否是用 TS 编写的,最终都要编译成 JS 代码,才能发布给开发者使用。

浅尝Typescript(二) 我们知道是 TS 提供了类型,才有了代码提示和类型保护等机制。

但在项目开发中使用第三方库时,你会发现它们几乎都有相应的 TS 类型,这些类型是怎么来的呢? 类型声明文件

类型声明文件

用来为已存在的 JS 库提供类型信息

TS 中有两种文件类型:

1 .ts 文件

2 .d.ts 文件

.ts 文件:

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

.d.ts 文件:

  1. 只包含类型信息的类型声明文件
  2. 不会生成 .js 文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型
  3. 第三方的声明文件(需要安装),一般会跟随第三方包一起安装,支持ts的,都会自带;
  4. 用途:为 JS 提供类型信息

17.内置类型声明文件

●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)

18.第三方库类型声明文件

  • 目前,几乎所有常用的第三方库都有相应的类型声明文件
  • 第三方库的类型声明文件有两种存在形式:1 库自带类型声明文件 2 由 DefinitelyTyped 提供。
  1. 库自带类型声明文件:比如,axios
    • 查看 node_modules/axios 目录

解释:这种情况下,正常导入该库,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。

  1. 由 DefinitelyTyped 提供
  • DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
  • DefinitelyTyped 链接
  • 可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*
  • 比如,@types/react、@types/lodash 等
  • 说明:在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示
import _ from 'lodash'

// 在 VSCode 中,查看 'lodash' 前面的提示
  • 解释:当安装 @types/* 类型声明包后,TS 也会自动加载该类声明包,以提供该库的类型声明
  • 补充:TS 官方文档提供了一个页面,可以来查询 @types/* 库
  • @types/* 库

19.类型声明-自定义的文件

目标

掌握对自定义文件提供类型声明文件

场景

如下两种场景需要提供类型声明文件

  1. 项目内共享类型
  2. 为已有 JS 文件提供类型声明

项目内共享类型

文件目录:

a.ts
b.ts

文件内容:

浅尝Typescript(二)

改进

将公共的类型定义提取出来,写在index.d.ts文件中

index.d.ts
a.ts
b.ts

定义接口,并导出

export interface Token {
  token: string
  refresh_token: string
}

a.js 导入接口并使用

import { Token } from './index' // 必须省略.d.ts

function showToken(token: )

20.类型声明-自定义的文件-为已有 JS 文件提供类型声明

背景

有一个ts项目,用到了.js文件。

项目中ts和js同时存在,且不想将.js改成.ts

demo.ts
utils/index.js

utils/abc.js

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 }

demo.ts

import { add } from './utils'  // 这里可以使用,但是,没有提示。

这里可以使用,但是,没有提示

如何基于现有的.js提供声明文件?

思路:编写同名的.d.ts文件

demo.ts
utils/index.js
utils/index.d.ts // 这里是重点

定义类型声明文件

  1. 它的作用是提供声明,不需要提供逻辑代码;

  2. declare 关键字:用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。

    a. 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。 b. 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。

declare let count:number

declare let songName: string

interface Position {
  x: number,
  y: number
}

declare let position: Position

declare function add (x :number, y: number) : number

type Direction = 'left' | 'right' | 'top' | 'bottom'

declare function changeDirection (direction: Direction): void

type FomartPoint = (point: Position) => void

declare const fomartPoint: FomartPoint

export {
  count, songName, position, add, changeDirection, fomartPoint
}

20.Axios与Typescript

问题

缺少提示

const getChannel = async () => {

  const res = await axios.get<{data:{channels: []}}>('http://geek.itheima.net/v1_0/channels')
  console.log(res.data.data.channels)
}

getChannel()

解决

type ChannelRes = {
  data: {channels: {id: number, name: string} []},
  message: string
}
const getChannel = async () => {

  const res = await axios.get<ChannelRes>('http://geek.itheima.net/v1_0/channels')
  console.log(res.data.data.channels)
}

getChannel()

今天就浅尝到这了,困了

浅尝Typescript(二)