前端【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,则认为校验通过

 

posted @ 2024-04-20 18:05  为你编程  阅读(10)  评论(0编辑  收藏  举报