文章目录
- 认识TypeScript
- 1. 什么是TS
- 2. 带来的好处
- 搭建TS编译环境
- 1. 为什么需要编译环境
- 2. 搭建手动编译环境
- 3. 搭建工程化下的自动编译环境
- 类型注解
- 1. TS类型注解是什么
- 2. TS支持的常用类型注解
- 3. 简单类型如何进行类型注解
- 数组类型注解
- 1. 有什么用
- 2. 如何注解数组类型
- 3. 思考题
- 联合类型和别名类型
- 1. 联合类型
- 2. 类型别名
- 3. 思考题
- 函数类型
- 1. 基础使用
- 2. 函数表达式
- 3. 可选参数
- 4. 无返回值 - void
- 5. 思考题
- interface接口
- 1. 接口类型的作用
- 2. 典型场景
- 3. 接口的可选设置
- 4. 接口的继承
- 5. 思考题
- type注解对象类型
- 1. 注解对象
- 2. type + 交叉类型模拟继承
- 3. interface 对比 type
- 4. 思考题
- 字面量类型
- 1. 什么是字面量类型
- 2. 使用场景
- 3. 思考题
- 类型推论和any类型
- 1. 类型推论
- 2. any类型
- 3. 类型断言
- 4. 类型断言的注意事项
- 泛型
- 1. 什么是泛型
- 2. 泛型接口
- 3. 泛型别名
- 4. 泛型函数
- 5. 泛型约束
- 综合案例
认识TypeScript
TS官网:学习一门语言,官网就是必不可少的资料
1. 什么是TS
TypeScript 是具有类型语法的 JavaScript,是一门强类型的编程语言。
2. 带来的好处
- 静态类型检查,提前发现代码错误
- 良好的代码提示,提升开发效率
搭建TS编译环境
1. 为什么需要编译环境
TypeScript编写的代码是无法直接在js引擎(浏览器/NodeJs)中运行的,最终还需要经过编译变成js代码才可以正常运行
带来的好处:既可以再开发时使用TS编写代码享受类型带来的好处,同时保证实际运行的还是JS代码
2. 搭建手动编译环境
-
全局安装 typescript 包(编译引擎)-> 注册 tsc 命令,可参考TS官网
npm install -g typescript
-
新增 hello.ts 文件, 执行 tsc hello.ts 命令生成hello.js文件
-
执行 node hello.js 运行js文件查看效果
3. 搭建工程化下的自动编译环境
基于工程化的TS开发模式(webpack / vite),TS的编译环境已经内置了,无需手动安装配置,创建项目参考vite官网
npm create vite@latest ts-pro -- --template vanilla-ts
命令说明:
- npm create vite@latest 使用最新版本的vite创建项目
- ts-pro 项目名称
- – --template vanilla-ts 创建项目使用的模板为原生ts模板
类型注解
1. TS类型注解是什么
let msg:string
msg='this is msg'
概念
类型注解指的是给变量添加类型约束,使变量只能被赋值为约定好的类型, 同时可以有相关的类型提示
说明
string 就是类型注解,
约束变量msg 只能被赋值为string 类型, 同时可以有string类型的相关提示
2. TS支持的常用类型注解
JS已有类型
- 简单类型
number
string
boolean
null
undefined
- 复杂类型
数组 、函数
TS新增类型
联合类型、类型别名、接口(interface)、字面量类型、泛型、枚举、void、any等
3. 简单类型如何进行类型注解
简单类型的注解完全按照 JS的类型(全小写的格式)来书写即可
let a:number=1
let b:string='你好'
let c:boolean=true
let d:null=null
let e:undefined=undefined
数组类型注解
1. 有什么用
变量被注解为数组类型之后,有俩点好处:
- 不仅可以限制变量类型为数组而且可以限制数组成员的类型
- 编码时不仅可以提示数组的属性和方法而且可以提示成员的属性和方法
2. 如何注解数组类型
使用数据类型对变量进行类型注解有俩种语法
//1.类型[]
let arr1:number[]
arr1=[1,2]
//2.泛型语法
let arr2:Array<number>
arr2=[1,2,3]
3. 思考题
有一个变量arr, 要求用俩种方式添加类型注解,使其只能赋值一个成员都是字符串的数组?
联合类型和别名类型
1. 联合类型
let arr3:(string | number)[]
arr3=[18,'李四']
概念
将多个类型合并为一个类型对变量进行注解
需求
如何注解数组类型可以让数组中既可以存放string类型的成员也可以存放number类型的成员?
说明
string | number
表示arr3中的成员既可以是string类型也可以是number类型
2. 类型别名
type Item=(string | number)[]
let arr5:Item=[20,'ceshi']
let arr6:number | string[]
arr6=5
arr6=['你好']
概念
通过 type关键词 给写起来较复杂的类型起一个其它的名字,用来简化和复用类型
说明
type 类型别名 = 具体类型
其中类型别名的命名采用规范的大驼峰格式
3. 思考题
有一个变量foo,要求添加类型注解,使其既可以赋值为number类型,也可以赋值为成员都是字符串的数组?
函数类型
1. 基础使用
function add(a:number,b:number):number{
return a+b
}
add(1,5)
概念
函数类型是指给函数添加类型注解,本质上就是给函数的参数和返回值添加类型约束说明
- 函数参数注解类型之后不但限制了参数的类型还限制了参数为必填
- 函数返回值注解类型之后限制了该函数内部return出去的值必须满足类型要求
2. 函数表达式
函数表达式的类型注解有俩种方式,参数和返回值分开注解和函数整体注解
-
参数和返回值分开注解
const unadd=(a:number,b:number):number=>{ return a-b } unadd(2,3)
-
函数整体注解(只针对于函数表达式)
type test =(a:number,b:number)=>number const testFn:test=(a,b)=>{ return a*b } testFn(2,5)
3. 可选参数
const Name=(firstName:string,lastName?:string):string=>{
if (!lastName){
lastName='先生'
}
return firstName+lastName
}
console.log(Name('白'));
console.log(Name('李', '四'));
概念
可选参数表示当前参数可传可不传,一旦传递实参必须保证参数类型正确
说明
lastName参数表示可选参数,可传可不传,一旦传递实参必须保证类型为string类型
4. 无返回值 - void
const vo=(arr:number[]):void=>{
return arr.forEach(item=>console.log(item))
}
vo([1,2,3])
概念
JS中的有些函数只有功能没有返回值,此时使用void进行返回值注解,明确表示函数没有函数值注意事项
在JS中如何没有返回值,默认返回的是undefined, 在TS中void和undefined不是一回事,undefined在TS中是一种明确的简单类型,如果指定返回值为undefined,那返回值必须是undefined类型
5. 思考题
编写一个arr2Str函数,作用为把数组转换为字符串,其中数组中既可以包含字符串和数字,分隔符也可以进行自定义,类型为字符串类型,使用样例:
- arr2Str( [1, 2, 3] , ‘-’ ) -> ‘1-2-3’
- arr2Str( [‘4’, ’5’] , ’&’ ) -> ‘4&5’
const arr1Str=(arr:(number|string)[],spilt:string):void=>{
console.log(arr.join(spilt));
}
arr1Str([8,9,10],'=')
interface接口
1. 接口类型的作用
interface Person{
name:string
age:number
}
const person:Person={
name:'李四',
age:28
}
作用
在TS中使用interface接口来描述对象数据的类型(常用于给对象的属性和方法添加类型约束)
说明
一旦注解接口类型之后对象的属性和方法类型都需要满足要求,属性不能多也不能少
2. 典型场景
场景
在常规业务开发中比较典型的就是前后端数据通信的场景
- 前端向后端发送数据:收集表单对象数据时的类型校验
interface loginForm{
username:string
password:string
}
let form:loginForm={
username:'Jack',
password:'123456'
}
- 前端使用后端数据:渲染后端对象数组列表时的智能提示
interface Food{
id:string
price:number
}
let food:Food[]=[
{
id:'1024',
price:15.8
}
]
food.forEach(item=>console.log(item.price))
3. 接口的可选设置
概念
和之前的可选参数很类似,通过?对属性进行可选标注,赋值的时候该属性可以缺失,如果有值必须保证类型满足要求
interface Apple{
color?:string
size:number
}
let apple:Apple={
size:16.8
}
let appleRed:Apple={
color:'red',
size:15.4
}
4. 接口的继承
概念
接口的很多属性是可以进行类型复用的,使用 extends 实现接口继承,实现类型复用
interface Food{
id:string
price:number
}
interface weFood extends Food{
weight:number
}
let we:weFood={
id:'2008',
price:15.6,
weight:12.5
}
5. 思考题
通常我们的后端接口返回的数据格式具有一定的规范,比如经常见到的response对象,如下,尝试使用interface接口定义其类型
interface Result{
code:number
msg:string
data:{
title:string
content:string
}
}
let result:Result={
code:200,
msg:'success',
data:{
title:'测试',
content:'李四'
}
}
console.log(result.data.content)
type注解对象类型
1. 注解对象
概念
在TS中对于对象数据的类型注解,除了使用interface之外还可以使用类型别名来进行注解,作用相似
type Per={
name:string
age:number
}
let p:Per={
name:'jack',
age:15
}
2. type + 交叉类型模拟继承
类型别名配合交叉类型(&)可以模拟继承,同样可以实现类型复用
type Foo={
id:string
name:string
}
type Fo = Foo & {
color:string
}
let f:Fo={
id:'156',
name:'西瓜',
color:'green'
}
console.log(f.color)
3. interface 对比 type
相同点
- 都能描述对象类型
- 都能实现继承,interface使用extends, type配合交叉类型
不同点
- type除了能描述对象还可以用来自定义其他类型
- 同名的interface会合并(属性取并集,不能出现类型冲突),同名type会报错
4. 思考题
对前面使用interface构建的response对象,通过type重构
type Data={
title:string
content:string
}
type Res={
code:number
msg:string
data:Data
}
let res:Res={
code:300,
msg:'success',
data:{
title:'标题',
content:'文本'
}
}
console.log(res.data.title)
字面量类型
1. 什么是字面量类型
概念
使用 js字面量 作为类型对变量进行类型注解,这种类型就是字面量类型, 字面量类型比普通的类型更加精确
说明
除了上面的数字字面量,js里常用的字符串字面量,数组字面量,对象字面量等都可以当成类型使用
//字面量类型,通常结合联合类型使用
type Sex = '男' | '女'
let sex: Sex = '男'
let str1 = 123
//const定义为常量,TS推断为字面量类型而不是number
const str2 = 123
type Msg = {
code: 1001 | 1002 | 1003
msg: string
}
let msg1: Msg = {
code: 1002,
msg: '你好'
}
2. 使用场景
场景1
性别只能是 ’男‘ 和 ’女‘,就可以采用联合类型配合字面量的类型定义方案
//字面量类型,通常结合联合类型使用
type Sex = '男' | '女'
let sex: Sex = '男'
let str1 = 123
//const定义为常量,TS推断为字面量类型而不是number
const str2 = 123
场景2
ElementUI中的el-button组件按钮的type属性
3. 思考题
还是我们熟悉的后端返回数据,这一次业务code码有多种情况1001、 1002、 1003,尝试改写类型满足要求
type Msg = {
code: 1001 | 1002 | 1003
msg: string
}
let msg1: Msg = {
code: 1002,
msg: '你好'
}
类型推论和any类型
1. 类型推论
概念
在 TS 中存在类型推断机制,在没有给变量添加类型注解的情况下,TS 也会给变量提供类型,以下是发生类型推断的几个场景
-
声明变量并赋值时
let cou = 200 cou = 100
-
决定函数返回值时
function add1(a: number, b: number) { return a + b }
2. any类型
作用
变量被注解为any类型之后,TS会忽略类型检查,错误的类型赋值不会报错,也不会有任何提示
注意
any的使用越多,程序可能出现的漏洞越多,因此不推荐使用 any 类型,尽量避免使用
let four: any
four = 100
four = 'pok'
let bar: number
bar = four
3. 类型断言
作用
有些时候开发者比TS本身更清楚当前的类型是什么,可以使用断言(as)让类型更加精确和具体
需求
获取页面中的id为link的a元素,尝试通过点语法访问href属性
const link = document.getElementById('link') as HTMLAnchorElement
link.href
4. 类型断言的注意事项
类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,滥用类型断言可能会导致运行时错误
说明
利用断言把foo变量的类型指定为精确的number,但是传参的时候还是可以传递number类型或者string类型均满足类型要求,但是传递string会导致运行时错误
//传参为string会出错,string没有toFixed方法
function test(foo: string | number) {
console.log((foo as number).toFixed(2))
}
泛型
1. 什么是泛型
概念
泛型(Generics)是指在定义接口、函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,使用泛型可以复用类型并且让类型更加灵活
2. 泛型接口
语法
在接口类型的名称后面使用即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型
通用思路
- 找到可变的类型部分通过泛型抽象为泛型参数(定义参数)
- 在使用泛型的时候,把具体类型传入到泛型参数位置 (传参)
interface message<T> {
code: number
msg: string
data: T
}
interface user {
username: string
password: string
}
interface food {
id: number
foodName: string
}
let mess1: message<user> = {
code: 200,
msg: '156',
data: {
username: '白',
password: '123456'
}
}
let mess2: message<food> = {
code: 550,
msg: '999',
data: {
id: 111,
foodName: 'apple'
}
}
3. 泛型别名
语法
在类型别名type的后面使用即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型
需求
使用泛型别名重构ResData案例
type wen<T> = {
code: number
msg: string
data: T
}
type user1 = {
username: string
password: string
}
type food1 = {
id: number
name: string
}
let weLi1: wen<user1> = {
code: 250,
msg: '123',
data: {
username: '里',
password: '123456'
}
}
let weLi2: wen<food1> = {
code: 200,
msg: '123',
data: {
id: 999,
name: '456789'
}
}
4. 泛型函数
语法
在函数名称的后面使用即可声明一个泛型参数,整个函数中(参数、返回值、函数体)的变量都可以使用该参数的类型
需求
设置一个函数createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型)
// @ts-ignore
let creatArr = []
function creatArray<T>(param: T) {
creatArr.push(param)
}
type user2 = {
code: number
msg: string
}
let user2 = {
code: 200,
msg: 'success'
}
creatArray<number>(123)
// @ts-ignore
console.log(creatArr);
creatArray<user2>(user2)
// @ts-ignore
console.log(creatArr);
5. 泛型约束
作用
泛型的特点就是灵活不确定,有些时候泛型函数的内部需要访问一些特定类型的数据才有的属性,此时会有类型错误,需要通过泛型约束解决
//添加约束
type LengthObj = {
length: number
}
function logLen<T extends LengthObj>(obj: T) {
console.log(obj.length);
}
logLen({length: 10})
logLen([1, 2])
综合案例
需求
记录当前页面的刷新次数和刷新时的时间,每次刷新都自动自增一次,并显示到页面中,要求用TypeScript实现
核心思路
- 从本地获取到当前最新列表,取出当前列表中的最后一条记录
- 在最后一条记录的基础上把次数加一,重新把次数和当前时间添加到列表的尾部
- 把最新列表渲染到页面
- 把最新列表再次存入本地
const KEY = 'ts-key'
/**
* 获取最新列表
*/
function getList(): GetList[] {
//JSON.parse转数组
return JSON.parse(localStorage.getItem(KEY) || '[]')
}
type GetList = {
count: number
time: string
}
/**
* 获取当前时间
*/
function getNowTime() {
const now = new Date()
return now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds()
}
/**
* 存储到本地,使用JSON.stringify转为字符串
* @param list
*/
function save(list: GetList[]) {
localStorage.setItem(KEY, JSON.stringify(list))
}
/**
* 刷新页面
* @param list
*/
function render(list: GetList[]) {
//遍历最新列表获取数据
list.forEach(Item => console.log('刷新次数为:' + Item.count + ',刷新时的时间为:' + Item.time))
const strArr = list.map(item => {
return `刷新次数为:${item.count},刷新时时间为:${item.time}`
})
//获取div容器,断言类型为HTMLDivElement
const app = document.getElementById('app') as HTMLDivElement
app.innerHTML = strArr.join('<br>')
}
function fresh() {
//1.获取最新数据列表
const list = getList()
//2.取列表最后一项数据
const lastItem = list[list.length - 1]
//3.count+1后添加到列表
list.push({
count: lastItem ? lastItem.count + 1 : 1,
time: getNowTime()
})
//4.刷新页面
render(list)
//5.存储到本地
save(list)
}
fresh()