TypeScript中的类型注解、Interface接口、泛型
一、认识TypeScript
1.概述
TypeScript是具有类型语法的JavaScript,是一门强类型的编程语言。它是 JavaScript 的超集(js中的所有元素都属于ts),这意味着任何有效的 JavaScript 代码本身也是有效的 TypeScript 代码。
2.优点
静态类型检查
TypeScript 在编译阶段就可以检查类型错误,而不像 JavaScript 是在运行时检查。例如,在 JavaScript 中,你可以这样写代码:
let x = 5;
x = "hello";
这段代码在 JavaScript 中是完全合法的,但是可能会导致一些难以发现的错误。在 TypeScript 中,你需要先定义变量的类型。
let x: number = 5;
x = "hello";//会报错,因为x被定义为数字类型,不能赋值为字符串
这种静态类型检查有助于在开发早期发现错误,提高代码的质量和可维护性。
3.用处
TypeScript不是万能的,技术的选项不能脱离具体的业务与应用场景,TS更适合用来开发中大型的项目,或者是通用的js代码库,再或者是团队写作开发的场景
二、搭建TypeScript环境
1.为什么需要搭建环境
TypeScript编写的代码无法直接在js引擎(浏览器/Node.js)中运行,最终还是需要编译成js代码才能运行。
带来的好处:既可以在开发时使用TypeScript编写代码享受类型带来的好处,同时也保证实际运行的还是javaScript代码
2.搭建编译环境
-
全局安装TypeScript包(编译引擎)
-
npm install -g typescript
-
-
将TypeScript文件编译,生成JavaScript文件
-
tsc xxx.ts
-
-
执行node xxx.js 运行
三、类型注解
1.TypeScript类型注解是什么
概念:类型注解是指给变量添加类型约束,使得变量只能被赋值为约定好的数据类型,同时还可以有相关的类型提示
说明:
:string
就是类型注解,约束变量message只能被赋值为string类型,同时可以有string类型相关的提示
//类型注解入门
let msg:string
msg = "Hello World";
let count:number;
count = 100;
let isLading:boolean
isLading = true;
2.TypeScript支持的常用类型注解
js已有的数据类型
- 简单类型
- number、string、boolean、null、undefined
- 复杂类型
- 数组、函数
TypeScript新增类型
联合类型、类型别名、接口(interface)、字面量类型、泛型、枚举、void、any等
2.1 简单类型
简单类型的注解完全按照js的类型(全小写)来书写
//简单类型进行类型注解
let age:number = 18;
let username:string = 'jacklove';
let isLoading:boolean = false;
let nullValue:null = null;
let undefinedValue:undefined = undefined;
2.2 数组类型
变量被注解为数组类型后,有两点好处
- 不仅可以限制变量类型为数组,且可以限制数组成员的类型
- 编码的时候不仅可以提示数组的属性和方法且可以提示成员的属性和方法
//数组类型注解的两种方法:
1.第一种(推荐)
let arr1:number[] = [1,2,3]
arr.forEach(item => console.log(item))
2.第二种(泛型写法)
let arr2:Array<number> = [1,2,3]
//输出步骤:当前文件夹打开cmd,输入
tsc 02数组类型类型注解.ts
node 02数组类型类型注解.js
1 2 3
2.3联合类型
概念:将多个类型合并为一个类型对变量进行注解
需求:如何注解数组类型可以让数组类型中既可以放string类型,游客与放number类型
说明:
number | string
表示arr3中的成员既可以是string类型也可以是number类型
//联合类型
let arr3: (number | string)[] = [1, 2, 3, 4, 5,'jack'];
arr3.push(6);
arr3.push('a');
2.4函数类型
给函数添加类型注解,本质上给函数的参数和返回值添加类型约束
说明:
- 函数参数注解类型之后不但限制了参数的类型还是限制了参数为必填
- 函数返回值注解类型之后,限制了该函数内部必须有return出去的值,才能满足类型要求
参数和返回值分开注解以及函数整体注解
//参数和返回值分开注解1
function add(a:number,b:number):number{
return a+b;
}
console.log(add(1,2));
//参数和返回值分开注解2
const add2 = (c:number,d:number):number =>{
return c+d;
}
console.log(add(1,2));
//函数整体注解
type AddFn = (c:number,d:number) => number
const addFunction1:AddFn = (a,b) => {
return a + b;
}
console.log(addFunction1(1,2));
2.5字面量类型
概念:使用JavaScript字面量作为类型对变量进行类型注解,这种类型就是字面量类型,字面量类型比普通类型更加精确
说明:除了下面的数字字面量,javaScript里面常用的字符串字面量、数组字面量、对象字面量都可以成为类型使用
//普通number类型,可以赋值任何数值
let count:number
count = 100
count = 200
//字面量类型
let count1:100 | 200 //只能赋值为100
//let count1:100 | 200 //只能赋值为100或200
count1 = 100
// count1 = 200 //报错
字面量类型的实际应用
场景1:性别只能是“男”或“女”,就可以采用联合类型配合字面类的类型定义方案
type gender ='男'|'女'
let sex:gender = '(提示男或女)'
场景2:ElementUI (饿了么UI)中的el-buttong组建的type属性
type Props = {
type: 'primary' | 'success' | 'danger' | 'warning'
}
字面量类型与const
const声明的变量称之为常量,常量是不可以重新赋值的,所以str2推断出来的是字面量类型而不是string类型
let str1 = 'this is string'
const str2 = 'this is const string'
2.6any类型
作用:变量别注释为any类型之后,TypeScript会忽略类型检查,错误的类型赋值不会报错,也不会有任何提示
注意:any的使用越多,程序可能出现的漏洞就越多,因此不推荐使用any类型,尽量避免使用
let obj:any = 100;
obj = "hello";
obj = {age:18}
obj = [18,20,'jack']
const n:number = obj;
3.类型别名
概念:通过Type关键字给写起来比较复杂的类型起一个其它的名字,用来简化和复用类型
说明: type 类型别名= 具体类型。
其中类型别名的命名方式采用规范的大驼峰写法
//类型别名
let arr:(string | number)[] = [18,'jack'];
//起别名
type ItemType = (string | number)[]
//使用类型别名
let array1:ItemType = [18,'jack'];
let array2:ItemType
//对于已经声明的变量,再赋值的时候就不用去重复声明数据类型
array2 = [18,'jack'];
4.可选参数
概念:可选参数指的是当前参数可传也可以不传,一旦传递实参必须保证参数类型正确
//可选参数:可以传也可以不传,如果传就必须类型正确
function builderName(firstName:string,lastName?:string){
if(lastName){
return `${firstName} - ${lastName}`
} else {
return firstName;
}
}
console.log(builderName('张'));
console.log(builderName('张','三丰'));
5 .无返回值 - void
概念:javaScript中有些函数只有功能没有返回值,此时使用void进行返回值的注解,明确表示函数没有返回值
注意事项:在JavaScript中如果没有返回值,默认返回的是
undefined
,在TypeScript中void和undefined不是一回事,undefined在typeScript中式一种明确的简单类型,如果指定返回值为undefined,那么返回值必须写undefined
//无返回值
function eachArr(arr:number[]):void{
arr.forEach(item => {
console.log(item)
})
//return ; //只写个renturn表示函数结束
}
eachArr([1,2,3,4,5])
6.Interface接口
6.1接口的作用
在TypeScript中使用interface接口来描述对象数据的类型(常用于给对象的属性和方法添加类型约束)
说明:一旦注解接口类型之后,对象的属性和方法类型都需要满足要求,属性不能多也不能少
//接口的作用
interface Person{
name:string
age:number,
height:number
}
const p:Person = {
name:'zhangsan',
age:18,
height:180
}
6.2应用场景
场景:在常规的业务开发中比较典型的就是前后端数据通信的场景
- 前端向后端发送数据:收集表单对象数据时的类型校验
- 前端使用后端数据:渲染后端对象数列表时的智能提示
6.3接口的继承
概念:接口的很多属性是可以进行类型复用的,使用
extends
实现接口继承,实现类型的复用
//原价商品类型
interface GoodsType {
id:string
price:number
}
//打折商品类型
// interface DisGoodTpye{
// id:string
// price:number
// disPrice:number
// }
interface DisGoodTpye extends GoodsType{
disPrice:number
}
7.type注解对象类型
7.1注解对象
概念:在TypeScript中对于对象数据的类型注解,除了使用interface之外还可以使用类型别名来进行注解,作用类似
type Person = {
name:string,
age:number
}
const p:Person = {
name:'mark',
age:39
}
7.2type+交叉类型模拟继承
类型别名配合交叉类型(&)可以模拟继承、同样可以实现类型复用
//父接口
type GoodsType = {
id:string
price:number
}
//子接口继承
type DisGoodType = GoodsType & {
disPrice:number
}
let goods:DisGoodType = {
id:'1',
price:100,
disPrice:90
}
console.log(goods.disPrice);
7.3 interface对比type
- 相同点
- 都能描述对象类型
- 都能实现继承,interface使用extends。type配合交叉类型(&)
- 不同点
- type除了能描述对象还可以用来自定义其它类型
- 同名interface会合并(属性取并集,不能出现类型冲突),同名type会报错
在注解对象类型的场景下非常相似,推荐大家一律使用tpye。type更加灵活
8.类型推断
类型推断::在TypeScript中存在类型推断机制,在没有给变量添加类型注解的情况下,TypeScript也会给变量提供类型
9.类型断言
作用:有些时候开发者比TypeScript本身更清楚当前的类型是什么,就可以使用断言(
as
)让类型更加精确和具体类型断言只能够【欺骗】TypeScript编译器,无法避免运行的时候报错,滥用类型断言会导致运行时错误
说明:利用断言把foo变量的类型指定为精确的number,但是传参的时候还是可以传递number或者是string类型均满足类型要求,但是传递string类型的时候会导致运行时错误
function log(foo:string | number){
//toFixed:保留几位小数
console.log((foo as number).toFixed(2));
}
log(100.2345);
log(100)
log('100')
10.泛型
10.1泛型概述
概念:泛型是指在定义接口、函数等类型的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性,使用泛型可以复用类型并且让类型更加灵活
10.2泛型语法
语法:再接口类型的而名称后面使用
<T>
即可声明一个泛型参数,接口里面的其它成员都能使用该参数类型通用思路:
- 找到可变的类型部分通过泛型抽象为反向参数(定义参数)
- 在使用泛型的时候,把具体的类型传入到泛型参数位置(传参)
infterface ResData<T> { }
//什么是泛型
interface ResData<T>{
}
//定义了一个具体的类型
interface User{
name:string
age:number
}
//使用泛型并传入具体类型
let userData:ResData<User> = {
code:2000,
msg:'成功',
data:{
name:'张三',
age:18
}
}
//什么是泛型
interface ResData<T>{
}
//定义具体的类型
interface Goods{
goodsName:string
price:number
}
//使用泛型并传入具体的类型
let goodData:ResData<Goods> = {
code:2000,
msg:'成功',
data:{
goodsName:'苹果',
price:18
}
}
10.3泛型别名
语法:在类型别名type的使用即可声明一个泛型参数,接口里的其它成员都使用该参数类型
type ResData<T> = { }
案例:需求:使用泛型类型重构ResData案例
//泛型别名
//定义泛型
type ResData<T> = {
msg:string
code:number
data:T
}
//定义具体类型
type User = {
name:string
age:number
}
//使用泛型传入具体的类型
let userData:ResData<User> = {
msg:'success',
code:200,
data:{
name:'jack',
age:18
}
}
//定义具体类型
type Goods = {
id:number
goodsName:string
}
//使用泛型传入具体的类型
let GoodsData:ResData<Goods> = {
msg:'success',
code:200,
data:{
id:1,
goodsName:'火龙果'
}
}
10.4泛型函数
语法:在函数名称的后面使用 即可声明一个泛型函数,整个函数中(参数、返回值和函数体)的变量都可以使用该参数的类型
function fn<T> () { }
案例:需求:设置一个函数createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型)
function createArray<T>(len:number,value:T){
let result:Array<T> = [];
for(let i = 0; i < len; i++){
result[i] = value;
}
return result;
}
console.log(createArray<number>(4,100));
console.log(createArray<string>(4,'a'));
10.5泛型约束
作用:反向的特点就是灵活不确定,有些时候泛型函数的内部需要访问一些特定类型的数据才有属性,此时会有类型错误,需要通过泛型的约束解决
四、综合案例
需求:记录当前页面的刷新次数和刷新时间,每次刷新都自动加1,并记录,要求使用typescript实现
- 从本地获取当前最新列表,取出当前列表中的第一条记录
- 在最后面的一条记录的基础上把次数加1,重新把次数和当前的时间记录到列表的末尾
- 把最新列表渲染到页面
- 把最新的列表存入到本地
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div></div>
<script src="./25.综合案例.js"></script>
</body>
</html>
//时间处理函数
function formateTime() {
var _data = new Date();
var h = _data.getHours();
var m = _data.getMinutes();
var s = _data.getSeconds();
return "".concat(h, ":").concat(m, ":").concat(s);
}
console.log(formateTime());
var KEY = 'ts-key';
//在浏览器中写入次数和时间
function setData(data) {
//在localStorage支持存入字符串,将对象转换成字符串
//JSON.stringify(对象); 可以将对象转换成json格式字符串
localStorage.setItem(KEY, JSON.stringify(data));
}
//从浏览器中取出
function getData() {
//需要将取出的字符串转换成对象对象,JSON.parse(json格式的字符串) 转换成对象
return JSON.parse(localStorage.getItem(KEY) || '[]');
}
// setData([{count:1,time:'16:34:01'}])
// console.log(getData());
//核心业务
//记录当前页面的刷新次数和刷新时间,每次刷新都自动加1,并记录,要求使用typescript实现
function updateData() {
//1.获取当前的列表
var list = getData();
//2.渠道上一条记录
var lastItem = list[list.length - 1];
//3.基于上一条记录count自增,然后把新的数据添加到数组的末尾
//加的是一个DateItem的对象
list.push({
count: lastItem ? lastItem.count + 1 : 1,
time: formateTime()
});
//4.把最新的列表存储到本地
setData(list);
}
updateData();
//数据在页面中展示
let div:HTMLElement = document.querySelector('div') as HTMLElement;
function getStr():string{
let newStr:string = '';
getData().forEach(item => {
newStr += `<p>访问时间:${item.time},访问次数:${item.count}</p>`
})
return newStr;
}
let str = getStr();
div.innerHTML = str;