浅尝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 }
- 类型变量 T,是一种特殊类型的变量,它处理类型而不是值
- 因为 T是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
- 类型变量 T,可以是任意合法的变量名称
调用格式
const 返回值 = 泛型函数名<类型1,类型2,...>(实参1,实参2,实参3,......)
语法:在函数名称的后面添加 <>(尖括号),尖括号中指定具体的类型
示例
const num = fn<number>(10)
const str = fn<string>('a')
- 当传入类型 number 后,这个类型就会传递给函数声明时对应的类型变量
- 通过泛型就做到了让 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
vue3配合ts中,还需要额外安装一个vscode插件:Typescript Vue Plugin
09 ts下使用props
之前 父传子,把props传递给子组件内部使用。可以通过defineProps来接收 defineProps配合vue默认语法进行类型校验(运行时声明)
<script setup>
// 运行时声明
const props = defineProps({
money: {
type: Number,
required: true
},
car: {
type: String,
default: '小黄车'
}
})
</script>
配合TS使用
- 类型校验
defineProps配合ts的泛型定义props,这样更直接
// 使用ts的泛型指令props类型
const props = defineProps<{
money: number
car?: string
}>()
- props可以通过解构来指定默认值
<script lang="ts" setup>
// 使用ts的泛型指令props类型
const { money, car = '小黄车' } = defineProps<{
money: number
car?: string
}>()
</script>
注意:
如果提供的默认值需要在模板中渲染,需要额外添加配置
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
10.ts下使用emits
- defineEmits配合运行时声明
<script lang="ts" setup>
const emit = defineEmits(['change', 'update'])
</script>
- defineEmits配合ts 类型声明,可以实现更细粒度的校验
const emit = defineEmits<{
(e: 'changeMoney', money: number): void
(e: 'changeCar', car: string): void
}>()
11.ts下使用ref设置初始值
目标
掌握ref配合ts如何使用
要点
- 通过泛型指定value的值类型,如果是简单值,该类型可以省略
- 如果是复杂类型,推荐指定泛型
示例
// 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如何使用
基本示例 不写任何类型,会报错,但是可以跑起来
两种做法
- 在template中,明确传入指定$event
- 在回调函数中指定参数的类型
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用于获取页面中元素时,有两个状态:
- 初始转态 Null
- 绑定后的状态
改进
const imgRef = ref<HTMLImageElement | null>(null)
onMounted(() => {
console.log(imgRef.value?.src)
})
技巧:如何查看一个DOM对象的类型:通过控制台进行查看
document.createElement('img').__proto__
16. TypeScript类型声明文件
背景
今天几乎所有的 JavaScript 应用都会引入许多第三方库来完成任务需求。 这些第三方库不管是否是用 TS 编写的,最终都要编译成 JS 代码,才能发布给开发者使用。
我们知道是 TS 提供了类型,才有了代码提示和类型保护等机制。
但在项目开发中使用第三方库时,你会发现它们几乎都有相应的 TS 类型,这些类型是怎么来的呢? 类型声明文件
类型声明文件
用来为已存在的 JS 库提供类型信息
TS 中有两种文件类型:
1 .ts
文件
2 .d.ts
文件
.ts 文件:
既包含类型信息又可执行代码
-
- 可以被编译为 .js 文件,然后,执行代码
- 用途:编写程序代码的地方
.d.ts 文件:
只包含类型信息
的类型声明文件- 不会生成 .js 文件,仅用于提供类型信息,在.d.ts文件中不允许出现可执行的代码,只用于提供类型
- 第三方的声明文件(需要安装),一般会跟随第三方包一起安装,支持ts的,都会自带;
- 用途:为 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 提供。
- 库自带类型声明文件:比如,axios
-
- 查看
node_modules/axios
目录
- 查看
解释:这种情况下,正常导入该库,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。
- 由 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.类型声明-自定义的文件
目标
掌握对自定义文件提供类型声明文件
场景
如下两种场景需要提供类型声明文件
- 项目内共享类型
- 为已有 JS 文件提供类型声明
项目内共享类型
文件目录:
a.ts
b.ts
文件内容:
改进
将公共的类型定义提取出来,写在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 // 这里是重点
定义类型声明文件
-
它的作用是提供声明,不需要提供逻辑代码;
-
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()
今天就浅尝到这了,困了
转载自:https://juejin.cn/post/7118347480349016077