typescript
TypeScript 概述
概念
TypeScript是具有类型语法的javascript,是一门强类型的编程语言。
TypeScript是微软开发的开源编程语言,Type
+ JavaScript(type是类型 => 在 JS 基础之上,为 JS 添加了类型支持),简称:TS**。**
// TS 代码
// 变量age1是强类型的,有明确的类型。即: number(数值类型)
let age1: number = 18
age1 = '18' // 报错
// --------------------
// javascript代码
// 变量age2是弱类型的 无明确的类型
let age2 = 18
age2 = '18' // 不报错
好处
- 静态类型检查,提前发现代码错误
- 良好的代码提示,提升开发效率
https://www.typescriptlang.org/zh/play?#code/DYUwLgBAhgXBB2BXAtgIxAJwgXggRgAYCAoKHCAJiKA
运行第一个TS代码
安装编译 TS 的工具包
TypeScript编写的代码是无法直接在js引擎(浏览器/Nodejs)中运行的,最终还需要经过编译成js代码才可以正常运行。
带来的好处: 既可以在开发时使用TS编写代码享受类型带来的好处,同时保证运行的还是JS代码。
搭建手动编译环境
- 全局安装 typescript 包(编译引擎)-> 注册 tsc
命令npm install -g typescript
- 新增 hello.ts 文件, 执行
tsc hello.ts
命令生成hello.js文件 - 执行 node hello.js 运行js文件查看效果
搭建自动编译环境
基于工程化的TS开发模式(webpack / vite),TS的编译环境已经内置了,无需手动安装配置,通过以下命令即可创建一个最基础的自动化的TS编译环境
npm create vite@latest ts-pro -- --template vanilla-ts
在线体验版本:https://stackblitz.com/edit/vitejs-vite-eg8x4m?file=package.json&terminal=dev
命令说明:
- npm create vite@latest 使用最新版本的vite创建项目
- ts-pro 项目名称
- – --template vanilla-ts 创建项目使用的模板为原生ts模板
拓展: 运行命令的来源
社区模板:
https://cn.vitejs.dev/guide/#scaffolding-your-first-vite-project
https://cn.vitejs.dev/guide/#community-templates
https://github.com/vitejs/awesome-vite#templates
类型注解
概念:类型注解指的是给变量添加类型约束,它的好处是:
- 使变量只能被赋值为约定好的类型
- 编写代码的过程中可以有相关的类型提示
示例代码:
let age: number = 18
说明:代码中的 : number
就是类型注解
作用:为变量添加类型约束。比如,上述代码中,约定变量 age 的类型为 number 类型
解释:约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错
约定了类型之后,代码的提示就会非常的清晰
TS支持的常用类型注解
-
JS 已有类型
-
- 原始类型,简单类型(
number/string/boolean/null/undefined
) - 复杂数据类型(数组,对象,函数等)
- 原始类型,简单类型(
-
TS 新增类型
-
- 联合类型
- 自定义类型(类型别名)
- 接口
- 元组
- 字面量类型
- 枚举
- void
- …
简单类型如何进行类型注解
简单类型的注解完全按照 JS的类型(全小写的格式)来书写即可
类型推论
问:每个简单的变量都要写类型注解么?
let age: number = 18
let myName: string = '小花'
let isLoading: boolean = false
类型推论
某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型
换句话说:由于类型推论的存在,有些场合下的类型注解可以省略不写
发生类型推论的 2 种常见场景:
- 声明变量并初始化时
- 决定函数返回值时
示例代码
// 变量 age 的类型被自动推断为:number
let age = 18
// 函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
return num1 + num2
}
技巧
- 能省略类型注解的地方就省略(偷懒,充分利用TS类型推论的能力,提升开发效率)
- 如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型
数组类型注解
注解数组有什么用
变量被注解为数组类型之后,有两点好处:
- 不仅可以限制变量类型为数组而且可以限制数组成员的类型
- 编码时不仅可以提示数组的属性和方法而且可以提示成员的属性和方法
如何注解数组类型
使用数据类型对变量进行类型注解有两种语法
语法1:
上面的代码表示: 变量arr只能用来保存数组类型,并且数组的元素必须是number类型
语法2:泛型函数
联合类型
数组中既有 number 类型,又有 string 类型
let arr: (number | string)[] = [1, 'a', 3, 'b']
一个变量, 它的类型可能是null也可能是number。
let a:(null|number) = null
a = 1
联合类型
概念:将多个类型合并为一个类型对变量进行注解
解释:|
(竖线)在 TS 中叫做联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种
**注意:**这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(|| 或)混淆了
类型别名-type
const arr1:(number | string)[] = [1, '1']
const arr2:(number | string)[] = ['a', 'b']
如上代码中,存在类型注解重复的问题: (number | string)[]
这个类型出现了多次, 怎么优化?
类型别名
定义: type 别名 = 类型
别名可以是任意的合法字符串,一般首字母大写
例子: type MyArr = (number | string) []
作用:给类型起别名 —> 定义了新类型
使用:
type MyArr = (number | string) []
const arr1:MyArr = [1, '1']
const arr2:MyArr = ['a', 'b']
函数类型
1. 基础使用
概念:函数类型是指给函数添加类型注解,本质上就是给函数的参数和返回值添加类型约束
说明:
- 函数参数注解类型之后不但限制了参数的类型还限制了参数为必填
- 函数返回值注解类型之后限制了该函数内部return出去的值必须满足类型要求
好处:
- 避免因为参数不对导致的函数内部逻辑错误
- 对函数起到说明的作用
2. 函数表达式
函数表达式的类型注解有两种方式:
- 参数和返回值分开注解
- 函数整体注解
参数和返回值分开注解
函数整体注解(只针对于函数表达式)
函数可选参数
概念:可选参数表示当前参数可传可不传,一旦传递实参必须保证参数类型正确
说明:lastName参数表示可选参数,可传可不传,一旦传递实参必须保证类型为string类型
可选参数一定要在参数列表的末尾
函数无返回值 - void
概念:JS中的有些函数只有功能没有返回值,此时使用void进行返回值注解,明确表示函数没有函数值
注意事项:
-
在JS中如果没有返回值,默认返回的是undefined
-
在TS中 void和undefined不是一回事:
-
**a.undefined在TS中是一种明确的简单类型,如果指定返回值为undefined,那返回值必须是undefined类型b.**void表示没有返回值
interface-接口-基本使用
作用: 用interface来描述对象数据的类型(常用于给对象的属性和方法添加类型约束)
说明:一旦注解接口类型之后对象的属性和方法类型都需要满足要求,属性不能多也不能少
典型场景
场景:在常规业务开发中比较典型的就是前后端数据通信的场景
- 前端向后端发送数据:收集表单对象数据时的类型校验
参考: https://element-plus.org/zh-CN/component/form.html#表单校验
2.前端使用后端数据:渲染后端对象数组列表时的智能提示
interface接口的可选设置和继承
对象的某个属性是可选的。
例如:从后端取回的商品数据中,一定有id,name, price,但是imgUrl是可选的。表示有些商品没有配图片。
const goodList = [
{
id:1, name: '手机', price: 2999, imgUrl: 'http://w.g1.png'},
{
id:1, name: '毛巾', price: 9}
]
我们这么约定goodList的格式,下面的代码会报错:
interface goodItem {
id: number;
name: string;
price: number;
imgUrl: string;
}
const goodList: goodItem[] = [
{
id:1, name: '手机', price: 2999, imgUrl: 'http://w.g1.png'},
{
id:1, name: '毛巾', price: 9} // 这里会报错
]
可选设置
概念: 通过 ?对属性进行可选标注,赋值的时候该属性可以缺失,如果有值必须保证类型满足要求。
格式:
interface 接口名{
属性1:类型1,
属性2?:类型2, // 属性2是可选的
属性3?:类型3, // 属性3是可选的
}
例如:
interface goodItem {
id: number;
name: string;
price: number;
imgUrl?: string; // 可选的
}
const goodList: goodItem[] = [
{
id:1, name: '手机', price: 2999, imgUrl: 'http://w.g1.png'},
{
id:1, name: '毛巾', price: 9} // 这里也是正确的
]
接口的继承
概念:接口的很多属性是可以进行类型复用的,使用 extends 实现接口继承,实现类型复用。
// 正常的商品
interface goodItem {
id: number;
name: string;
price: number;
imgUrl?: string;
}
// 打折的商品:正常商品 + newPrice + effectDate
interface goodItemDiscount {
id: number;
name: string;
price: number;
imgUrl?: string;
newPrice: number;
effectDate: Date
}
继承之后
interface goodItemDiscount extends goodItem {
newPrice: number;
effectDate: Date
}
type注解对象类型
注解对象
概念:在TS中对于对象数据的类型注解,除了使用interface之外还可以使用类型别名来进行注解,作用相似
type + 交叉类型模拟继承
类型别名配合交叉类型(&)可以模拟继承,同样可以实现类型复用
interface 对比 type
相同点
- 都能描述对象类型
- 都能实现继承,interface使用extends, type配合交叉类型
不同点
- type除了能描述对象还可以用来自定义其他类型
- 同名的interface会合并(属性取并集,不能出现类型冲突)
3.同名type会报错
在注解对象类型的场景下非常相似,推荐大家使用type, type更加灵活
字面量类型
以下代码,两个变量的类型分别是什么?
let str1 = 'abc'
const str2 = 'abc'
通过 TS 类型推论机制,可以得到答案:
- 变量 str1 的类型为:string
- 变量 str2 的类型为:‘abc’
解释:
- str1 是一个变量(let),它的值可以是任意字符串,所以类型为:string
- str2 是一个常量(const),它的值不能变化只能是 ‘abc’,所以,它的类型为:‘abc’
注意:此处的 ‘abc’,就是一个字面量类型,也就是说某个特定的字符串也可以作为 TS 中的类型,任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用
字面量类型的实际应用
字面量类型在实际应用中通常和联合类型结合起来使用,提供一个精确的可选范围
场景1:性别只能是 ’男‘ 和 ’女‘,就可以采用联合类型配合字面量的类型定义方案
场景2:ElementUI中的el-button组件按钮的type属性
any 类型
当值的类型为 any 时,可以对该值进行任意操作,并且不会有代码提示
let obj: any = {
x: 0 } // any 就是任意类型
obj.bar = 100
obj()
const n: number = obj
解释:
- 以上操作都不会有任何类型错误提示,即使可能存在错误
两种隐式 any 类型的情况
- 声明变量不提供类型也不提供默认值
- 函数参数不加类型
原则:
- 不推荐使用 any! 这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)
- 除非临时使用 any 来“避免”书写很长、很复杂的类型
类型断言
问题
获取页面中的id为link的a元素,尝试通过点语法访问href属性
发现代码报错了
原因是:getElementById方法返回值的类型是 HTMLElement,而这个类型只包含所有标签公共的属性或方法,这个类型太宽泛(不具体),不包含 a 标签特有的 href 等属性。
类型断言
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言(as)来指定更具体的类型
应用场景:把一个大类型 缩小 为更加具体的类型
格式:类型 as 具体类型
使用类型断言:
解释:
- 使用
as
关键字实现类型断言 - 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)
- 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了
例如:
在发请求拿数据的过程中有一种典型的场景:我们提前知道了后端返回的数据的类型,也定义了个变量用来保存数据,把他的初值为{}。但是,下面的代码会报错
type Res = {
total: number,
list: object[]
}
// 我们确定当ajax数据回来之后,result的数据就是上面的Res格式
// 但是,它的初值为 {}
const result:Res = {
} as Res
泛型
后端接口给前端返回数据时,一般会遵循一定的规律,例如:都有code, msg, data三个属性,而不同的接口返回的具体数据保存在data中,它的格式是不同的。
如下是两个典型的返回结果:
// 请求得到用户信息
const res1 = {
code: 200, msg: 'success', data: {
name: 'jack', age: 18}}
// 请求得到商品信息
const res2 = {
code: 200, msg: 'success', data: [{
id: 1001, goodsName: '衬衣'}}]
示例代码: 定义两个不同的接口
interface Res1 {
code: number,
msg: string,
data: {
name: string,
age: number
}
}
interface Res2 {
code: number,
msg: string,
data: {
goodsName: string,
id: number
}[]
}
const res1: Res1 = {
}//
const res2: Res2 = {
}//
问题:两种类型有重复的代码。
interface Res1<T> {
code: number,
msg: string,
data: T
}
const res1: Res1<{
name: string, age: number}> = {
code:10,msg:'123',data:{
name: '小花',age:18} }
const res2: Res1<{
goodsName: string, id: number}[]> = {
code:10,msg:'123',data:[{
goodsName: '小花',id:18}] }
概念
泛型(Generics)是指在定义接口、函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性**,** 使用泛型可以复用类型并且让类型更加灵活。
泛型接口
语法:在接口类型的名称后面使用<T>
即可声明一个泛型参数列表,接口里的其他成员都能使用该参数的类型。
类比:函数的形参
通用思路:
- 找到可变的类型部分通过泛型抽象为泛型参数(定义参数)
泛型参数相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
2.在使用泛型的时候,把具体类型传入到泛型参数位置 (传参)
泛型-类型别名
语法:在类型别名type的后面使用<T>
即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型。
type Res11<T> = {
code: number,
msg: string,
data:T
}
type Res22 = {
name:string,age:number}
type Res33 = Res11<Res22>
const res11: Res33 = {
code:10,msg:'123',data:{
name: '小花',age:18} }
type Res222 = {
goodsName: string,id: number}[]
type Res333 = Res11<Res222>
const res22: Res333 = {
code:10,msg:'123',data:[{
goodsName: '小花',id:18}] }
泛型-函数
需求:定义一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型)
function createArray(len, initValue) {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = initValue
}
return
}
createArray(4,'a') // 得到一个长度为4的数组,它的每个元素都是'a'
createArray(3,1) // 得到一个长度为3的数组,它的每个元素都是1
语法:在函数名称的后面使用即可声明泛型参数,整个函数中(参数、返回值、函数体)的变量都可以使用该参数的类型
function fn<T>(){
}
function createArray<T>(length: number, initValue: T): T[] {
let result:T[] = [];
for (let i = 0; i < length; i++) {
result[i] = initValue
}
return result
}
createArray(4, 'a') // 得到一个长度为4的数组,它的每个元素都是'a'
createArray(3, 1) // 得到一个长度为3的数组,它的每个元素都是1
泛型约束
作用:泛型的特点就是灵活不确定,有些时候泛型函数的内部需要访问一些特定类型的数据才有的属性,此时会有类型错误,需要通过泛型约束解决
下面是一个报错的例子:
泛型约束的格式
<T extends 具体类型>
添加约束