前端【TS】05-typescript【基础】【简单类型】【联合类型】【类型别名】【函数类型】【可选参数、无返回值】【接口类型】【对象类型】【字面量类型】【断言类型】【泛型】【TS内置类型Omit和Pick】【typeof、keyof】
一、介绍
TypeScript 是具有类型语法的 JavaScript,是一门强类型的编程语言
带来的好处
① 静态类型检查,提前发现代码错误
② 良好的代码提示,提升开发效率
什么时候用
以下是来自社区的一些建议:1. 你做的是一个大型的应用吗?
2. 是否是团队协作开发模式? 多一句没有,少一句不行,用更短时间,教会更实用的技术!
3. 是否在编写通用的代码库?(Vue3 / ElementPlus...)
结论:TypeScript不是万能的,技术的选型不能脱离具体的业务和应用场景,TS更加适合用来开发中大型的项目,或 者是通用的JS代码库,再或者是团队协作开发的场
为什么需要编译环境?
TypeScript编写的代码是无法直接在js引擎(浏览器/NodeJs)中运行的,最终还需要经过编译变成js代码才可以正常 运行
搭建手动编译环境
1 1. 全局安装 typescript 包(编译引擎)-> 注册 tsc 命令 2 npm install -g typescript 3 2. 新增 hello.ts 文件, 执行 tsc hello.ts 命令生成hello.js文件 4 3. 执行 node hello.js 运行js文件查看效果
搭建工程化下的自动编译环境
基于工程化的TS开发模式(webpack / vite),TS的编译环境已经内置了,无需手动安装配置,通过以下命令即可创 建一个最基础的自动化的TS编译环境
1 npm create vite@latest ts-pro -- --template vanilla-ts 2 3 // 命令说明: 4 1. npm create vite@latest 创建vue3项目 使用最新版本的vite创建项目 5 2. ts-pro 自定义的项目名称 6 3. -- --template vanilla-ts 创建项目使用的模板为原生ts模板
二、语法
TS支持的常用类型注解
JS已有类型
1. 简单类型
number string boolean null undefined
2. 复杂类型
数组 函数
TS新增类型
联合类型、类型别名、接口(interface)、字面量类型、泛型、枚举、void、any等
注解简单类型
简单类型的注解完全按照 JS的类型(全小写的格式)来书写即可
1 let age: number = 100; 2 let name: string = '张三'; 3 let isLoading: boolean = true; 4 let unllValue: null = null; 5 let undefinedValue: undefined = undefined;
注解数组类型
1 // 注解数组的方式1:推荐 2 let ageArr: number[] = [12, 18, 20]; // 表示变量ageArr只能赋值数组类型并且数组的成员必须都是number类型 3 4 // 注解数组的方式2:泛型 5 let nameArr: Array<string> = ['张三', '李四']
注解联合类型
将多个类型合并为一个类型对变量进行注解
1 // 联合类型 2 // (string | number) 表示arr是数组,并且其中的成员既可以是string类型也可以是number类型 3 let arr: (string | number)[] = ['jack', 20] 4 5 // 去掉小括号,含义就变了,表示arr可以是string类型,也可以是number[]类型 6 let arr: string | number[] = ['jack', 20]
类型别名
通过 type关键词 给写起来较复杂的类型起一个其它的名字,用来简化和复用类型
1 // 定义类型别名 2 type twoType = (string | number)[] 3 // 使用类型别名 4 let arr2: twoType = ['jack', 20] 5 let arr3: twoType = ['jack', 50, '老王']
函数类型
函数类型是指给函数添加类型注解,本质上就是给函数的参数和返回值添加类型约束
说明:
1. 函数参数注解类型之后不但限制了参数的类型还限制了参数为必填
2. 函数返回值注解类型之后限制了该函数内部return出去的值必须满足类型要求
好处:
1. 避免因为参数不对导致的函数内部逻辑错误
2. 对函数起到说明的作用
1 // 函数类型 2 // 限制参数个数、类型以及返回值类型 3 function add(a: number, b: number): number { 4 return a + b 5 }
函数表达式(箭头函数)
1 // 方式1、参数和返回值分开注解 2 const addFunction = (a: number, b: number): number => a + b 3 4 // 方式2、函数整体注解(只针对于函数表达式) 5 // 函数类型别名 6 type addFunType = (a: number, b: number) => number 7 // 使用函数整体注解 8 const addFunction2: addFunType = (a, b) => a + b
可选参数
可选参数表示当前参数可传可不传,一旦传递实参必须保证参数类型正确
1 /** 2 * 可选参数 3 * lastName?: string 就是一个可选参数,调用函数的时候可以传可以不传 4 */ 5 function buildName(firstName: string, lastName?: string): string { 6 if (lastName) { 7 return `${firstName} - ${lastName}` 8 } else { 9 return firstName 10 } 11 } 12 13 console.log(buildName('张三')) 14 console.log(buildName('张三', '李四'))
无返回值 - void
JS中的有些函数只有功能没有返回值,此时使用void进行返回值注解,明确表示函数没有函数值
1 /** 2 * 无返回值的函数,类型使用void 3 */ 4 function printArr(arr: number[]): void { 5 arr.forEach((item) => console.log(item)) 6 } 7 8 printArr([1, 2, 3, 4])
注意事项:在JS中如何没有返回值,默认返回的是undefined, 在TS中void和undefined不是一回事,undefined在TS 中是一种明确的简单类型,如果指定返回值为undefined,那返回值必须是undefined类型
interface接口类型
在TS中使用interface接口来描述对象数据的类型(常用于给对象的属性和方法添加类型约束)
说明:一旦注解接口类型之后对象的属性和方法类型都需要满足要求,属性不能多也不能少
1 /** 2 * 接口类型 3 */ 4 interface Person { 5 name: string, 6 age: number 7 } 8 9 let p1: Person = { 10 name: '张三', 11 age: 20 12 } 13 console.log(p1)
interface的典型场景
场景:在常规业务开发中比较典型的就是前后端数据通信的场景
1. 前端向后端发送数据:收集表单对象数据时的类型校验
2. 前端使用后端数据:渲染后端对象数组列表时的智能提示
接口的可选设置
通过?对属性进行可选标注,赋值的时候该属性可以缺失,如果有值必须保证类型满足要求
1 /** 2 * 接口类型 3 */ 4 interface Person { 5 name: string, 6 age?: number // 设置age为可选属性, 也就是在赋值的时候,可以传也可以不传,但是传了就必须为定义的number类型 7 } 8 9 // 传可选字段age 10 let p1: Person = { 11 name: '张三', 12 age: 20 13 } 14 console.log(p1) 15 16 // 不传可选字段age 17 let p2: Person = { 18 name: '张三' 19 } 20 console.log(p2)
接口的继承
接口的很多属性是可以进行类型复用的,使用 extends 实现接口继承,实现类型复用
1 /** 2 * 1、分开定义不同类型 3 */ 4 // 商品类型 5 interface GoodsType { 6 goodsName: string, // 商品名称 7 goodsPrice: number // 商品价格 8 } 9 10 // 打折商品类型 11 interface DisGoodsType { 12 goodsName: string, // 商品名称 13 goodsPrice: number, // 商品价格 14 goodsDisPrice: number // 商品打折价格 15 } 16 17 /** 18 * 2、通过继承定义这两个类型 19 */ 20 interface GoodsType { 21 goodsName: string, // 商品名称 22 goodsPrice: number // 商品价格 23 } 24 25 // 继承GoodsType定义的属性和类型 26 interface DisGoodsType extends GoodsType { 27 goodsDisPrice: number // 商品打折价格 28 } 29 30 let disGoods: DisGoodsType = { 31 goodsName: "气球", 32 goodsPrice: 20, 33 goodsDisPrice: 5 34 }
type注解对象
在TS中对于对象数据的类型注解,除了使用interface之外还可以使用类型别名来进行注解,作用相似
1 // 使用type注解对象 2 type GoodsType = { 3 goodsName: string, // 商品名称 4 goodsPrice: number // 商品价格 5 } 6 let goods: GoodsType = { 7 goodsName: "气球", 8 goodsPrice: 20, 9 } 10 console.log(goods)
type + 交叉类型模拟继承
类型别名配合交叉类型(&)可以模拟继承,同样可以实现类型复用
1 type GoodsType = { 2 goodsName: string, 3 goodsPrice: number 4 } 5 // 使用类型别名 配合 & 模拟继承(实现类型复用) 6 type DisGoodsType = GoodsType & { 7 goodsDisPrice: number 8 } 9 10 let disGoods: DisGoodsType = { 11 goodsName: "气球", 12 goodsPrice: 20, 13 goodsDisPrice: 5 14 } 15 console.log(disGoods)
interface 对比 type
相同点
1. 都能描述对象类型 多一句没有,少一句不行,用更短时间,教会更实用的技术!
2. 都能实现继承,interface使用extends, type配合交叉类型
不同点
1. type除了能描述对象还可以用来自定义其他类型
2. 同名的interface会合并(属性取并集,不能出现类型冲突),同名type会报错
在注解对象类型的场景下非常相似,推荐大家一律使用type, type更加灵活
① 同名的interface会取并集
1 /** 2 * 同名的interface会取并集 3 */ 4 interface Item { 5 name: string 6 } 7 8 interface Item { 9 age: number 10 } 11 12 // 此时定义Item类型,就必须name和age都传并且类型不能错 13 let item: Item = { 14 name: '张三', 15 age: 20 16 } 17 console.log(item)
② type定义的类型重名,会直接报错
什么是字面量类型
使用 js字面量 作为类型对变量进行类型注解,这种类型就是字面量类型, 字面量类型比普通的类型更加精确
说明:除了上面的数字字面量,js里常用的字符串字面量,数组字面量,对象字面量等都可以当成类型使用
字面量类型的实际应用
字面量类型在实际应用中通常和联合类型结合起来使用,提供一个精确的可选范围
场景1:性别只能是 ’男‘ 和 ’女‘,就可以采用联合类型配合字面量的类型定义方案
场景2:ElementUI中的el-button组件按钮的type属性
字面量类型与const
思考一下下面的 str1 和 str2,TS推断出来的类型分别是什么?
说明:const声明的变量称之为常量,常量是不可以进行重新赋值的,所以str2推断出来的是字面量类型而不是string 类型
类型推论
在 TS 中存在类型推断机制,在没有给变量添加类型注解的情况下,TS 也会给变量提供类型,以下是发生类型 推断的几个场景
1. 声明变量并赋值时,如果已经赋值,会根据赋的值推到其类型,一旦赋值推到完类型,后面就不能在赋值其他类型
2. 决定函数返回值时
一些小建议
1. 开发项目的时候,能省略类型注解的地方就省略
2. 刚开始学TS,建议对所有类型都加上,先熟悉
3. 鼠标放至变量上,VsCode 自动提示类型
any类型
变量被注解为any类型之后,TS会忽略类型检查,错误的类型赋值不会报错,也不会有任何提示
1 let obj: any = { age: 19 } 2 obj.bar = 100 // 当obj类型为any时,给他不存在的属性赋值也不会报错 3 obj() // 当obj类型为any时,把它当作一个函数也不会报错,但是运行会报错 4 const n: number = obj // 将obj赋值给一个类型为number的变量,也不会报错,因为obj为any类型
注意:any 的使用越多,程序可能出现的漏洞越多,因此不推荐使用 any 类型,尽量避免使用
断言类型
类型断言的基本使用
有些时候开发者比TS本身更清楚当前的类型是什么,可以使用断言(as)让类型更加精确和具体
需求:获取页面中的id为link的a元素,尝试通过点语法访问href属性
1 const link1 = document.getElementById('link') 2 // 报错提示 3 // 1、'link1' is possibly 'null' 变量link1可能为null 4 // 2、Property 'href' does not exist on type 'HTMLElement' 5 // console.log(link1.href) 6 7 8 const link2 = document.getElementById('link') as HTMLAnchorElement 9 link2.href // 此时不会提示报错,但是当运行时,如果link2为null还是会报错
类型断言的注意事项
类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,滥用类型断言可能会导致运行时错误
说明:利用断言把foo变量的类型指定为精确的number,但是传参的时候还是可以传递number类型或者string类型 均满足类型要求,但是传递string会导致运行时错误
1 function log(a: number | string): void { 2 // 断言a为number类型,这样就可以使用number类型的方法,但是一旦传入的a为其他类型,没有toFixed方法就会报错 3 console.log((a as number).toFixed(2)) 4 } 5 6 log(100) // 不报错,运行时也不报错 7 log('11111') // 不报错,运行时会报错
泛型
什么是泛型
泛型(Generics)是指在定义接口、函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型 的一种特性, 使用泛型可以复用类型并且让类型更加灵活
泛型接口
语法:在接口类型的名称后面使用即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型
通用思路:
1. 找到可变的类型部分通过泛型抽象为泛型参数(定义参数)
2. 在使用泛型的时候,把具体类型传入到泛型参数位置 (传参)
1 interface User { 2 name: string, 3 age: number 4 } 5 interface UserData { 6 code: number, 7 msg: string, 8 data: User 9 } 10 11 12 interface Goods { 13 id: number, 14 goodsName: string 15 } 16 interface GoodsData { 17 code: number, 18 msg: string, 19 data: Goods 20 } 21 22 /** 23 * 抽取泛型,泛型接口 24 */ 25 interface Data<T> { 26 code: number, 27 msg: string, 28 data: T 29 } 30 31 // User泛型 32 let result1: Data<User> = { 33 code: 200, 34 msg: '响应成功', 35 data: { 36 name: '张三', 37 age: 20, 38 } 39 } 40 // Goods泛型 41 let result2: Data<Goods> = { 42 code: 200, 43 msg: '响应成功', 44 data: { 45 id: 10000, 46 goodsName: '牙膏', 47 } 48 }
泛型别名
语法:在类型别名type的后面使用即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型
1 interface User { 2 name: string, 3 age: number 4 } 5 6 interface Goods { 7 id: number, 8 goodsName: string 9 } 10 11 /** 12 * 泛型别名 13 */ 14 type Data<T> = { 15 code: number, 16 msg: string, 17 data: T 18 } 19 20 // User泛型 21 let result1: Data<User> = { 22 code: 200, 23 msg: '响应成功', 24 data: { 25 name: '张三', 26 age: 20, 27 } 28 } 29 // Goods泛型 30 let result2: Data<Goods> = { 31 code: 200, 32 msg: '响应成功', 33 data: { 34 id: 10000, 35 goodsName: '牙膏', 36 } 37 }
泛型函数
语法:在函数名称的后面使用即可声明一个泛型参数,整个函数中(参数、返回值、函数体)的变量都可以使用该 参数的类型
需求:设置一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型)
1 // 需求:设置一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型) 2 3 function createArray<T>(length: number, value: T) { 4 let result = [] 5 for (let i = 0; i < length; i++) { 6 result[i] = value; 7 } 8 return result; 9 } 10 // 调用泛型函数, 指定泛型 11 createArray<number>(10, 100) // 创建一个类型为number的数组,长度为10,默认每个元素为100 12 createArray<string>(10, '张三') // 创建一个类型为string的数组,长度为10,默认每个元素为'张三'
泛型约束
作用:泛型的特点就是灵活不确定,有些时候泛型函数的内部需要访问一些特定类型的数据才有的属性,此时会有类 型错误,需要通过泛型约束解决
1 function logLen<T>(obj: T) { 2 console.log(obj.length) // 当使用了泛型函数,直接这么访问特定类型才有的属性时,会报错 3 } 4 5 // 引入泛型约束 ↓↓↓ 6 7 // 定义接口类型 8 interface LengthObj { 9 length: number 10 } 11 12 // 使用泛型约束,泛型T必须继承了接口LengthObj,这样就继承到接口中的length属性 13 function logLen<T extends LengthObj>(obj: T) { 14 console.log(obj.length) 15 }
综合案例
1 // TS CODE 2 export { } 3 4 // 综合案例 5 6 // 时间处理函数 7 function formatTime(): string { 8 const _date = new Date() 9 const h = _date.getHours() 10 const m = _date.getMinutes() 11 const s = _date.getSeconds() 12 return `${h}:${m}:${s}` 13 } 14 15 console.log(formatTime()) 16 17 // 基于localStorage封装存取函数 18 type DataItem = { 19 count: number 20 time: string 21 } 22 const KEY = 'ts-key' 23 function setData(data: DataItem[]) { 24 localStorage.setItem(KEY, JSON.stringify(data)) 25 } 26 27 function getData(): DataItem[] { 28 return JSON.parse(localStorage.getItem(KEY) || '[]') 29 } 30 31 // setData([{ count: 1, time: '10:10:10' }]) 32 // console.log(getData()) 33 34 // 核心业务处理函数 35 function updateData() { 36 // 1. 获取当前的列表 37 const list = getData() 38 // 2. 取到上一条记录 39 const lastItem = list[list.length - 1] 40 // 3. 基于上一条记录count自增,然后把新数据添加到列表末尾 41 list.push({ 42 count: lastItem ? lastItem.count + 1 : 1, 43 time: formatTime(), 44 }) 45 // 4. 把最新的列表存入本地 46 setData(list) 47 } 48 49 updateData()
Omit排除指定字段
1 export type User = { 2 /** token令牌 */ 3 token: string 4 /** 用户ID */ 5 id: string 6 /** 用户名称 */ 7 account: string 8 /** 手机号 */ 9 mobile: string 10 /** 头像 */ 11 avatar: string 12 } 13 14 // login登录register注册changeMobile更换手机号forgetPassword找回密码,bindMobile绑定三方登录 15 export type CodeType = 16 | 'login' 17 | 'register' 18 | 'changeMobile' 19 | 'forgetPassword' 20 | 'bindMobile' 21 22 type OmitUser = Omit<User, 'token'> 23 export type UserInfo = OmitUser & { 24 /** 关注 */ 25 likeNumber: number 26 /** 收藏 */ 27 collectionNumber: number 28 /** 积分 */ 29 score: number 30 /** 优惠券 */ 31 couponNumber: number 32 orderInfo: { 33 /** 待付款 */ 34 paidNumber: number 35 /** 待发货 */ 36 receivedNumber: number 37 /** 待收货 */ 38 shippedNumber: number 39 /** 已完成 */ 40 finishedNumber: number 41 } 42 } 43 44 // Omit Pick TS的内置类型 45 // type Person = { 46 // name: string 47 // age: number 48 // gender: 0 | 1 49 // } 50 // // Omit 是从对象中排出一些属性,得到对象类型 51 // type OmitPerson = Omit<Person, 'age' | 'gender'> 排除一些属性 52 // // Pick 是从对象中摘取一些属性,得到对象类型 53 // type PickPerson = Pick<Person, 'gender' | 'age'> 摘取保留一些属性 54 55 // 家庭档案-患者信息 56 export type Patient = { 57 /** 患者ID */ 58 id?: string 59 /** 患者名称 */ 60 name: string 61 /** 身份证号 */ 62 idCard: string 63 /** 0不默认 1默认 */ 64 defaultFlag: 0 | 1 65 /** 0 女 1 男 */ 66 gender: 0 | 1 67 /** 性别文字 */ 68 genderValue?: string 69 /** 年龄 */ 70 age?: number 71 } 72 73 // 家庭档案-患者信息列表 74 export type PatientList = Patient[]
tyoeof
1 import CpNavBar from '@/components/CpNavBar.vue' 2 import CpIcon from '@/components/CpIcon.vue' 3 import CpRadioBtn from '@/components/CpRadioBtn.vue' 4 import CpPaySheet from '@/components/CpPaySheet.vue' 5 6 declare module 'vue' { 7 interface GlobalComponents { 8 // 添加组件类型 9 CpNavBar: typeof CpNavBar // typeof 拿到对象的类型 10 CpIcon: typeof CpIcon 11 CpRadioBtn: typeof CpRadioBtn 12 CpPaySheet: typeof CpPaySheet 13 } 14 }
keyof
1 type Key = keyof PartialConsult // 拿到对象的所有字段的联合类型 2 onMounted(() => { 3 // 生成订单需要的信息不完整的时候需要提示 4 const validKeys: Key[] = [ 5 'type', 6 'illnessType', 7 'depId', 8 'illnessDesc', 9 'illnessTime', 10 'consultFlag', 11 'patientId' 12 ] 13 const valid = validKeys.every((key) => store.consult[key] !== undefined) // 遍历,使用其中的key,去stroe.consult中取指定字段的值,如果不为undefined,则认为校验通过