陪我看月亮,陪我学习 TypeScript,好吗?
0、TypeScript简介
-
TypeScript是JavaScript的超集。
-
它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
-
TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
-
TS完全兼容JS,换言之,任何的TS代码都可以直接当成JS使用。
-
为什么需要typeScript
- 在编译期间就可以发现错误
- 提高开发体验,有代码提示
- 强大类型系统 ,提高代码可维护性
- 支持最新的ES特性
- 面向对象
- 各大框架的加持
-
TypeScript练习题 github.com/type-challe…
1、TypeScript 开发环境搭建
-
下载Node.js
-
安装Node.js
-
使用npm全局安装typescript
- 进入命令行
- 输入:
npm i -g typescript
- 检查是否安装成功 命令行输入
tsc
-
创建一个ts文件
-
使用tsc对ts文件进行编译
- 进入命令行
- 进入ts文件所在目录
- 执行命令:tsc xxx.ts
2、基本类型
Ts在js已有类型上增加了 ==> 元组,枚举,any,类型断言,void,never
-
类型注解
-
简而言之,类型注解给变量设置了类型,使得变量只能存储某种类型的值
-
语法:
-
let 变量: 类型; let 变量 = 值; //变量的声明和赋值时同时进行的,可以省略掉类型声明 function fn(参数: 类型, 参数: 类型): 类型{ ... }
-
-
-
自动类型判断
- TS拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明
2.1 类型
类型 | 例子 | 描述 |
---|---|---|
number | 1, -33, 2.5 | 任意数字 |
string | 'hi', "hi" | 任意字符串 |
boolean | true、false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值 |
any | * | 任意类型 |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {name:'孙悟空'} | 任意的JS对象 |
array | [1,2,3] | 任意JS数组 |
tuple | [4,5] | 元组,TS新增类型,固定长度数组 |
enum | enum{A, B} | 枚举,TS中新增类型 |
2.2 原始数据类型
number
/string
/boolean
/null
/undefined
- 特点:简单,这些类型,完全按照 JS 中类型的名称来书写
let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false
let obj: null = null //null 类型的值只能是 null
let und: undefined = undefined // undefined 类型的值只能是 undefined
2.3 array (数组类型)
let list: number[] = [1, 2, 3];// 写法一:
let list: Array<number> = [1, 2, 3]; // 写法二:
2.4 字面量
-
使用模式:
字面量类型
配合联合类型
一起使用 -
使用场景:用来表示一组明确的可选值列表
-
结论
:相比于string类型,使用字面量类型更加精确,严谨// 使用自定义类型: type Direction = 'up' | 'down' | 'left' | 'right' function changeDirection(direction: Direction) { console.log(direction) } // 调用函数时,会有类型提示: changeDirection('up')
2.5 any(我不在乎它的类型)
如果声明变量不指定类型,则TS解析器会自动判断变量的类型any (又称隐式的any)
-
在实际开发过程中不建议使用any , 因为失去了TypeScript最大的类型系统
-
使用
any
之后,不会有任何类型错误提示,即使可能存在错误let d: any = 4; d = 'hello'; d = true;
- 除非:临时使用
any
来避免书写很长,很复杂的类型
2.6 unknown (我不知道它类型)
表示 未知类型的值
-
let notSure: unknown = 4; notSure = 'hello';
2.7 never
-
function error(message: string): never { throw new Error(message); }
2.8 object(没啥用)
-
let obj: object = {}; let b:{name:string} //语法 {属性名:属性值}用来指定对象中可以包含哪些属性 //在属性名后面加上 ? 表示属性是可选的 let b:{name:string,age?:number}; b={name:'孙悟空',age:18} //[propName:string]:any 表示任意类型的属性 let c:{name:string,[propName:string]:any} c={name:'猪八戒',age:18,gender:'男'}
2.9 tuple(元组 )
场景:当我们想定义一个数组中具体索引位置的类型时,可以使用元组
-
元组是另一种数组类型,只有
确切
的知道包含多个元素
,以及对应的类型 -
元组一旦指定,数组个数和对应位置的类型必须匹配
let x: [string, number]=["hello", 10];
//元组,元组就是固定长度的数组 //语法:[类型,类型,类型] let h:[string,number]; h=['hello',123]
2.10 enum(枚举)
使用 enum
关键字定义一组常量,它描述一组明确的可选值,
-
数字枚举
- 枚举成员的值为数字的枚举,称为:
数字枚举
- 枚举成员是有值的,默认从0开始自增的数值
enum Color { Red, Green, Blue, } //类似于JS中的对象,直接通过点(.)语法访问枚举的成员 let c: Color = Color.Green;
- 枚举成员的值为数字的枚举,称为:
-
字符串枚举
- 枚举成员的值是字符串
- 字符串枚举没有自增长行为,因此字符串枚举的每个成员
必须有初始值
eunm Direction { Up="Up" Down='DOWN' Left='LEFT' Right="RIGHT" }
-
枚举成员的值可以进行重新初始化的
//可以给枚举中的成员初始化值 enum Color { Red = 1, Green, Blue, } let c: Color = Color.Green;
-
实际开发中:更加推荐使用字面量+联合类型组合的方式
-
约定枚举名称,枚举中的值以大写字母开头
/使用以函数参数 eunm Direction { Up, Down, Left, Right } function changeDirection(direction:Direction){} //类似于JS中的对象,直接通过点(.)语法访问枚举的成员 changeDirection(Direction.Left)
2.11 symbol
作用 :可以作为对象属性的键,防止与对象中其他键冲突
-
Symbol() 创建的值是唯一的
let uniqkey:symbol=Symbol() let obj={ a:'123', [uniqkey]:100 } console.log(obj[uniqkey])
2.11 联合类型
-
如果定义的数据既可以是 number 类型,又可以是 string 类型
-
可以使用联合类型 :由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种
//数据中使用联合 let arr:Array<number | string>=[1,2,'张三'] //简化写法 此处()的目的是为了提升优先级,表示:number 类型的数组或 string类型的数组 let arr:(number|string)[]=[1,'a',3,'b'] // 应用:定义一个定时器变量 let timer: number | null = null timer = setInterval(() => {}, 1000)
2.12 类型别名(自定义类型)
场景:当同一类型(复杂)被多次使用时,可以通过
类型别名
,方便复用
-
语法:
type
类型名称=具体类型 -
推荐:大驼峰命名方式
-
创建类型别名后,直接使用该类型别名作为变量的类型注解即可
type Mng=number | string let my:Mng='1' type CustomArray=(number|string)[] let arr1:CustomArray=[1,'q',2,'f']
-
type可以替换 interface
type Person={ name:string age:number } let p:Person={ name:'cp', age:18 }
2.13 字面量类型配合联合类型
字面量类型配合联合类型
一起使用,用来表示一组明确的可选值列表
-
通过const 定义,有具体的值,这个值可以作为类型
-
相比于string 类型,使用字面量更加严谨和精确
-
场景 :组件props校验 限制用户传参的格式,提供几个固定的选项
//使用自定义类型 type Direction ='up'|'down'|'left'|'right' function changeDirection(direction:Direction){ console.log(direction) } // 调用函数时,会有类型提示: 参数只能是 up/down/left/right中的任意一个 changeDirection('up')
2.14 类型推论
TypeScript 里没有明确指出类型的地方,类型推断会帮助提供类型
-
注意
类型推论有时候不够精确,需要手动声明或者使用类型断言 -
能省略类型注解的地方就省略(
偷懒,充分利用TS类型推论的能力,提升开发效率) -
技巧
:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型let x=100 //x 自动推断为number let y='200' //y 自动推断为string function add(a:number,b:number){ return a+b } let z= add(1,2) // z自动推断为 number
2.15 非空断言
如果我们明确的知道对象的属性一定不会为空,那么可以使用非空断言 !
-
作用:如果明确知道对象的属性一定不为空,可以使用
!
来指定 -
语法
const imgRef = ref<HTMLImageElement | null>(null) onMounted(() => { imgRef.value!.src = '2.jpg' //!. 非空断言 })
注意:
- 非空断言一定要确保有该属性才能使用,不然使用非空断言会导致bug
2.16 类型断言
语法: 变量 as 类型
<类型> 变量
🍌基本使用
- 定义一个变量,默认存放空对象
- 对象中的属性来源于网络请求
- 使用对象中的属性
可以使用类型断言来指定更具体的类型
type Use = {
name: string
age: number
gender: string
}
// 指定类型之后,直接给 use 赋值空对象会报错
// 我们明确知道 use 对象就是 Use 类型
// 可以通过 as 来将空对象转为 use,以达到显示提示的目的
let use:Use = {} as Use
// 另一种用法
// let use:Use = <Use>{}
setTimeout(() => {
use = {
name: 'zs',
age: 18,
gender: '男'
}
}, 1000);
console.log(use.name)
🍉操作Dom
-
类型断言还在 dom 操作时用的比较多
// 直接获取 dom 之后,无法点出 link 中的属性 const aLink = document.getElementById('link') // 获取 dom 之后,将 link 断言为 HTMLAnchorElement,可以轻松点出属性 const aLink = document.getElementById('link') as HTMLAnchorElement
3.函数类型
函数的类型实际上指的是:函数的参数和返回值的类型
3.1 指定参数、返回值的类型
-
函数参数
&返回值
的类型// 函数声明 function add(num1: number, num2: number): number { return num1 + num2 } // 箭头函数 const add = (num1: number, num2: number): number => { return num1 + num2 }
3.2 指定整个函数类型
-
函数的类型完全一致,可以将函数类型进行封装
type AddFn = (num1: number, num2: number) => number const add: AddFn = (num1, num2) => { return num1 + num2 }
3.3 void 类型
如果一个函数没有返回值,此时,在 TS 的类型中,应该使用 void 类型
-
如果函数没有返回值,那么,函数返回值类型为:
void
function greet(name: string): void { console.log('Hello', name) }
// 如果什么都不写,此时,add 函数的返回值类型为: void const add = () => {} // 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同 const add = (): void => {} // 但,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以 const add = (): undefined => { // 此处,返回的 undefined 是 JS 中的一个值 return undefined }
3.4 可选参数
定义函数时,有些参数可传可不传,这种情况下就需要使用可选参数来指定类型
-
该函数有两个参数,其中的age参数在类型前面加了一个
?
,代表可选参数 -
注意
可选参数
要放到参数列表的最后 -
比如,数组的 slice 方法,可以 slice() 也可以 slice(1) 还可以 slice(1, 3)
//示例1 function showPersonIfo(name:string,age?:number) { console.log(name,age) } //示例2 function mySlice(start?: number, end?: number): void { console.log('起始索引:', start, '结束索引:', end) }
4.对象类型
4.1 基本使用
-
使用
{}
来描述对象结构 -
属性采用
属性名: 类型
的形式 -
方法采用
方法名(): 返回值类型
的形式// 空对象 let person: {} = {} // 有属性的对象 let person: { name: string } = { name: '同学' } // 既有属性又有方法的对象 // 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔 let person: { name: string; sayHi(): void } = { name: 'jack', sayHi() {} } // 对象中如果有多个类型,可以换行写: // 通过换行来分隔多个属性类型,可以去掉 `;` let person: { name: string sayHi(): void } = { name: 'jack', sayHi() {} }
4.2 任意属性
-
任意属性来实现可以少属性/多属性
interface Person { name: string; age?: number; [propName: string]: any; } let tom: Person = { name: 'Tom', gender: 'male' };
4.3 箭头函数参数类型
-
对象中箭头函数形式的参数类型定义
-
方法的类型也可以使用箭头函数形式
{ greet(name: string):string, greet: (name: string) => string } type Person = { greet: (name: string) => void greet(name: string):void } let person: Person = { greet(name) { console.log(name) } }
4.4 对象可选属性
-
对象的属性或方法,也可以是可选的,此时就用到可选属性了
-
比如,我们在使用
axios({ ... })
时,如果发送 GET 请求,method 属性就可以省略 -
可选属性的语法与函数可选参数的语法一致,都使用
?
来表示type Config = { url: string method?: string } function myAxios(config: Config) { console.log(config) }
4.5 使用类型别名
-
不推荐
:直接使用 {} 形式为对象添加类型,会降低代码的可读性(不好辨识类型和值) -
推荐
:使用类型别名为对象添加类型// 创建类型别名 type Person = { name: string sayHi(): void } // 使用类型别名作为对象的类型: let person: Person = { name: 'jack', sayHi() {} }
5.类(class)
5.1 定义类
class 类名 {
属性名: 类型;
constructor(参数: 类型){
this.属性名 = 参数;
}
方法名(){
....
}
}
🍌示例
class Person{
name: string;
age: number;
//构造函数 构造函数会在对象创建时调用
constructor(name: string, age: number){
//在实例方法中,this就表示当前的实例
this.name = name;
this.age = age;
}
sayHello(){
console.log(`大家好,我是${this.name}`);
}
}
🍎 使用类
const p = new Person('孙悟空', 18);
p.sayHello();
5.2 readonly(只读属性)
-
如果在声明属性时添加一个
readonly
,则属性便成了只读属性无法修改 -
在通过
const
进行常量声明,声明后不可以修改,但是如果值是一个引用类型的话,依旧可以对其内部的发展进行修改。所以在使用Typescript
当中的readonly
关键字对属性或者是变量进行声明,那么将会在编译时就发出告警const a:{readonly name:string}={name:'user'} a.name='1111'//会报错
5.3 public (默认值)
-
可以在类,子类和对象中修改
class Person{ public name: string; // 写或什么都不写都是public public age: number; constructor(name: string, age: number){ this.name = name; // 可以在类中修改 this.age = age; } sayHello(){ console.log(`大家好,我是${this.name}`); } } class Employee extends Person{ constructor(name: string, age: number){ super(name, age); this.name = name; //子类中可以修改 } } const p = new Person('孙悟空', 18); p.name = '猪八戒';// 可以通过对象修改
5.4 protected
-
可以在类、子类中修改
class Person{ protected name: string; protected age: number; constructor(name: string, age: number){ this.name = name; // 可以修改 this.age = age; } sayHello(){ console.log(`大家好,我是${this.name}`); } } class Employee extends Person{ constructor(name: string, age: number){ super(name, age); this.name = name; //子类中可以修改 } } const p = new Person('孙悟空', 18); p.name = '猪八戒';// 不能修改
5.5 private
-
可以在类中修改
class Person{ private name: string; private age: number; constructor(name: string, age: number){ this.name = name; // 可以修改 this.age = age; } sayHello(){ console.log(`大家好,我是${this.name}`); } } class Employee extends Person{ constructor(name: string, age: number){ super(name, age); this.name = name; //子类中不能修改 } } const p = new Person('孙悟空', 18); p.name = '猪八戒';// 不能修改
-
属性存取器
-
对于一些不希望被任意修改的属性,可以将其设置为private
-
直接将其设置为private将导致无法再通过对象修改其中的属性
-
我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器
-
读取属性的方法叫做setter方法,设置属性的方法叫做getter方法
-
示例:
class Person{ private _name: string; constructor(name: string){ this._name = name; } get name(){ return this._name; } set name(name: string){ this._name = name; } } const p1 = new Person('孙悟空'); console.log(p1.name); // 通过getter读取name属性 p1.name = '猪八戒'; // 通过setter修改name属性
-
5.6 static (静态属性)
使用static关键词标记的属性会变成类的静态成员,这些属性只存在于类本身
-
使用静态属性无需创建实例,通过类即可直接使用
class Tools{ static PI = 3.1415926; static sum(num1: number, num2: number){ return num1 + num2 } } console.log(Tools.PI); console.log(Tools.sum(123, 456));
5.7 extends (继承)
-
通过继承可以将其他类中的属性和方法引入到当前类中
-
通过继承可以在不修改类的情况下完成对类的扩展
-
重写
-
发生继承时,如果子类中的方法会替换掉父类中的同名方法,这就称为方法的重写
-
示例
class Animal{ name: string; age: number; constructor(name: string, age: number){ this.name = name; this.age = age; } run(){ console.log(`父类中的run方法!`); } } class Dog extends Animal{ bark(){ console.log(`${this.name}在汪汪叫!`); } run(){ console.log(`子类中的run方法,会重写父类中的run方法!`); } } const dog = new Dog('旺财', 4); dog.bark(); //旺财在汪汪叫!
-
5.8 super
-
在子类中可以使用
super
来完成对父类的引用class Animal{ name: string; age: number; constructor(name: string, age: number){ this.name = name; this.age = age; } //在类的方法中,super就表示当前类的父类 sayHello(){ console.log(`动物在叫!`); } } class Dog extends Animal{ //如果在子类中写了构造函数,在子类构造函数中必须调用父类构造函数 constructor(name: string, age: number){ super(name)//调用父类的构造函数 } sayHello(){ super.sayHello(); } } const dog = new Dog('旺财', 4); dog.sayHello(); //动物在叫
5.9 abstract (抽象类)
-
抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
abstract class Animal{ abstract run(): void; bark(){ console.log('动物在叫~'); } } class Dog extends Animals{ run(){ console.log('狗在跑~'); } }
🍎this
- 在类中,使用this表示当前对象
6 interface (接口)
当一个对象类型被多次使用时,可以使用 type 来描述对象的类型,达到复用的目的
- 定义一个
interface
,通常使用I字母打头的大驼峰命名法 - 变量一旦使用了接口类型之后,对对象的属性,属性类型 都有约束,属性不能多不能少,类型也不能错
- 当一个对象类型被多次使用时,可以使用 type 来描述对象的
interface IPerson {
name:string
age:number
sayHi(): void
}
let pi:IPerson ={
name:"cp",
age:30,
sayHi() {}
}
6.1 interface vs type
interface(接口)和 type(类型别名)的对比
-
相同点:都可以给对象指定类型
-
不同点:
-
interface (接口) :只能为对象指定类型
-
type(类型别名)
- 不仅可以为对象指定类型
- 还可以为任意类型指定别名
-
-
推荐
:能使用type 就是用 typeinterface IPerson { name: string age: number sayHi(): void } // 为对象类型创建类型别名 type IPerson = { name: string age: number sayHi(): void } // 为联合类型创建类型别名 type NumStr = number | string
6.2 可选属性
单独对一些属性进行额外的可选处理
interface IPerson {
name:string,
age?:number
}
// age属性是可选的,可有可无
let pi:IPerson ={
name:"cp"
}
6.3 只读属性 (readonly)
一些对象的属性不可以修改,只可以读,可以使用
readonly
修饰
interface IPerson {
readonly id:string,//只读属性
name:string,
age?:number
}
let p1:IPerson ={
id:'1001',
name:"cp"
}
p1.id="1002"
6.4 extends (继承接口)
接口也支持继承,让接口可以分割成可重用的模块
如果两个接口之间有相同的属性或方法,可以将公共的属性和方法抽离出来,通过继承来实现复用
interface Shape{
color:string
}
interface Square extends Shape{
sideLength:number
}
let s:Square={
color:'blue',
sideLength:100
}
🥭条件类型
如果
T
包含的类型是U
包含的类型的"子集" ,哪么取X
否则取结果Y
//类似 三元表达式
T extends U ?X:Y
🍍 例子
type NonNullable<T> = T extends null | undefined ? never : T;
// 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。
let demo1: NonNullable<number>; // => number
let demo2: NonNullable<string>; // => string
let demo3: NonNullable<undefined | null>; // => never
🥭 infer
infer
最早出现在此 PR 中,表示在 extends
条件语句中待推断的类型变量。
type ParamType<T> = T extends (param: infer P) => any ? P : T;
6.5 &(交叉类型)
使用交叉类型,来实现接口继承的功能
-
功能类似于接口继承
(extends)
,用于组合多个类型为一个类型(常用于对象类型)//使用 type 自定义类型来模拟 Point2d type Point2D={ x:number, y:number } // 使用交叉类型后,同时具有了 Point2D和后面的所有属性 type Point3D=Point2D&{ z:number } let o:Point3D={ x:1, y:2, z:3 }
6.6 &和extends的对比
交叉类型 &和接口继承(extends)的对比
-
相同点:都可以实现对象类型的组合
-
不同点:两种方式实现类型组合时,对于同名属性之间,
处理类型冲突的方式不同
-
extends(接口继承)
interface A{ fn :(value:number)=> string } interface B extends A{ // 报错 fn:(value:string)=>string }
-
& 交叉类型
interface A { fn:(value:number)=>string } interface B { fn:(value:string)=>string } type C=A&B
-
-
以上代码,接口继承会报错(类型不兼容),交叉类型没有错误,可以简单的理解为:
fn:(value:string|number)=>string
7.泛型 ( Generics )
定义一个函数或类时,无法确定其中使用的类型,此时泛型便能够发挥作用
泛型
:是可以在保证类型安全提,让函数等与多种类型一起工作,从而实现复用,常用于:函数 ,接口
,class中
-
在函数右侧声明占位类型 T是一般写法Type的写法,它代表一个类型变量而不是值 ,可以在调用函数的时候,传入具体的类型
-
type 类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
-
type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
// 创建泛型函数 function id<T>(value:T): T { return value } //使用泛型函数 //传入number类型 const numberId= id<number>(1001) //传入string类型 const stringID=id<string>("1002") // 简化:调用时可以省略类型,类型会由Ts自动推断出来 const ID=id(10)
7.1 泛型约束 (extends )
使用
extends
关键字来为 泛型函数添加类型约束
- 传入的实参只要有
length
属性即可 - 函数的参数为某一类型时,函数的返回值也为该类型
注意
-
泛型添加约束在我们写代码时不会使用
-
主要是使用别人封装的方法时会用到
interface ILength { length:number } function getArrLen<T extends ILength > (value:T):T{ // 这里的类型添加了约束,不再报错 console.log(value.length) return value } // const res1 = fn<Number>(1) // 报错 const res2 = fn('abc') const res3 = fn([1, 2, 3]) const res4 = fn({ length: 11 })
7.2 多个变量
语法:只需要在 < > 括号中,使用逗号分隔类型即可<T,U>
-
比如我们有一个函数接收两个成员的元组,然后返回成员类型对调的新元组
function switchTuple<T,U>(tuple:[T,U]):[U,T]{ return [tuple[1],tuple[0]] } const newTuple=switchTuple<number,string>([1,'1'])
-
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量的约束)
比如,创建一个函数来获取对象中属性的值
// keyof 取Type对象中的键 组成 联合类型 所以 key只能取Type中已有的键 也就是 name|age function getProp<Type,Key extends keyof Type>(obj:Tyep,key:key) getProp({name:'jack',age:'age'},'age')
7.3 泛型接口
泛型接口:接口也可以配合泛型来使用 作用:增加接口灵活性,增强其利用性
- 在接口名称的后面添加
<类型变量>
,那么,这个接口就变成了泛型接口。 - 使用泛型接口时,需要显式指定具体的类型
-
场景
//用户信息接口 let user={ code:200, msg:'个人信息', result:{ name:'cp' age:30 } } //用户列表接口 let userList={ code:200, msg:'个人信息列表' result:[ { name:'cp' age:30 }, { name:'cp' age:30 }, ] }
-
实现:思路找重复的地方,抽象成泛型
//定义用户信息掊口 interface User{ name:string, msg:number } //定义泛型接口() interface ApiRes<T> { code:number, msg:string, result:T } //验证 let user Apires<User>={ code:200, msg:'个人信息', result:{ name:'cp' age:30 } }
7.4 泛型类
//类型通用使用泛型
class Queue<T> {
private data:T[]=[]
add(item:T){
return this.data.push(item)
}
pop():T {
return this.data.shift()
}
}
const unmberQueue=new Queue<number>()
numberQueue.add(100)
8 泛型工具
TS内置一些常用的工具类型,来简化TS中的一些常见操作
- 基于泛型实现的内置工具(泛型适用于多种类型,更加通用)
8.1 Partial
作用:Partial用来构造一个类型,将Type的所有属性设置为
可选
type Props={
id:string,
children:number[]
}
//构造出来的新类型PartiaProps结构和Props相同,但是所有的属性都变为可选的
type PartiaProps=Partial<Props>
// let Obj:Props={id:'1'}//报错
let obj2:PartiaProps={id:'1'}//ok
8.2 Readonly
作用:Readonly用来构造一个类型,将所有的属性设置为
只读
type Props={
id:string,
children:number[]
}
type PartiaProps=Readonly<Props>
let Props:ReadonlyProps={id:'1',children:[]}
// 错误演示
props.id='2' //因为使用Readonly设置为只读属性
8.3 Pick
作用:从已存的接口中选择一组属性来构造新类型(Pick<Type,Keys>)
-
pick 工具类型有两个类型变量:1 表示选择谁的属性,2表示选择几个属性
-
第二个类型变量传入的属性只能是第一个类型变量中存在的属性
interface Props { id:string, title:string, children:number[] } //第二个类型变量传入的属性只能是第一个类型变量中存在的属性 type PickProps=Pick<Props,'id'|'title'> //构造出来的新类型pickProps,只有id和title两个属性类型 let obj:PickProps={ id:"1001", title:"this is title" }
8.4 Record
Record<Keys,Type> 构造一个
对象类型
,属性键为Keys,属性类型为Type
-
Record 工具类型有俩个类型变量,1. 表示对象有哪些属性,2. 表示对象value的类型
-
些类型与索引签名的作用一致
//构建的新对象类型RecordOb表示:这个对象有三个属性分别为a/b/c,属性值的类型都是string[] type RecordObj=Record<'a'|'b'|'c',string[]> let obj:RecordObj={ a:['1'], b:['2'], c:['3'] }
8.5 Exclude
用于过滤不需要的类型
//通过从所有可分配给的联合成员中排除来构造一个 Exclude 类型
type T0 = Exclude<"a" | "b" | "c", "a">;
type T0 = "b" | "c"
'
'
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
type T1 = "c"
9 索引签名类型
9.1对象
使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就可以用
索引签名类型
绝大多数情况下,可以在使用对象前就确定对象的结构,并为对象添加准确的类型
-
使用【key:string】约束该接口中允许出现的属性名称。表示只要是 string类型的属性名称,都可以出现对象中
-
前置知识:JS中对象({})的键是string类型的
interface AnyObject{
//key 只是一个占位符,可以换成任意合法的变量名称
[key:string]:number
}
let obj:AnyObject={
a:1,
b:2
}
interface AnyObject{ //可以限制后缀为 `x` [key:`${string}x`]:number }
let obj:AnyObject={ ax:1, bx:2 }
9.2 数组
在 JS中数组是一类特殊的对象,特殊在 数组的键
(索引)是数值类型 并且,数组也可以出现任意多个元素,所以,在数组对应的泛型接口中,也用到了索引签名类型
-
该索引签名类型表示:只要是
number
类型的键(索引)都可以出现在数组中,或者说数组中可以有多个任意元素 -
同时也符合数组索引是number类型这一前提
interface MyArray<T> { [n:number]:T } let arr:MyArray<number>=[1,3,5] arr[0]
10 映射类型
映射类型:基于旧类型创建新类型(对象类型),减少重复,提升开发效率
映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]
-
key in PropKeys 表示 key 可以是 PropKeys 联合类型中的任意一个,类似于 fonin (let k in obj)
-
比如,类型
PropKeys
有x/y/z,另一个类型Type1 x/y/z ,并且type1 中的x/y/z的类型相同type PropKeys ="x"|"y"|"z" type Type1={x:number;y:number;z:number} //简化上述写法 type PropKeys ="x"|"y"|"z" type Type2={ [key in PropKeys ]:number }
10.1 in(联合类型创建)
-
in 用于
取联合类型
的值。主要用于数组和对象的构造 -
注意:映射类型
只能在类型别名
中使用,不能在接口使用type name = 'firstName' | 'lastName'; type TName = { [key in name]: string; }; //使用in 相当于 type TName={ firstName:string lastName:string }
10.2 typeof (获取类型)
使用场景:在类型上下文中获取变量或属性的类型,来简化类型书写
-
通过
typeof
操作符获取p
变量类型 并赋值给point
类型变量let p={x:1,y:2} function formatPoint(point:{x:number;y:number}){} formatPoint(p) //此处使用 typeof 获取p变量类型 在赋值给 point function formatPoint2(point:typeof p){} formatPoint2({x:1,y:2})
-
注意:typeof只能用来查询
变量
或属性的
类型,无法查询其他形式的类型(比如,函数调用的类型)let P={x:1,y:2} let num:typeof p.x //num:string //不允许用来查询函数返回类型 function add(num1:number,num2:number){ return unm1+unm2 } let ret:typeof add(2,2) // 报错
10.3 keyof (对象类型创建)
根据对象类型创建
- 首先,执行
keyof
Props获取到对象类型 Props中所有键的联合类型,形成 ‘a'|'b'|'c' - 然后 key in ..... 就表示 可以是Props中所有的键名称中的任意一个
type Props={a:number;b:string;c:boolean}
type Type3={[key in Keyof Props ]:number} // key in ..... 就表示 可以是Props中所有的键名称中的任意一个
-
keyof
关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型//`keyof`与Object.keys略有相似,只是`keyof`是取 `interface` 的键后会保存为联合类型 interface iUserInfo { name: string; age: number; } type keys = keyof iUserInfo;
-
实现一个函数
getValue
取得对象的 Value。在未接触
Keyof
时function getValue(o: object, key: string) { return o[key]; } const obj1 = { name: '张三', age: 18 }; const name = getValue(obj1, 'name');
使用
keyof
j时function getValue<T extends Object, K extends keyof T>(o: T, key: K): T[K] { return o[key]; } const obj1 = { name: '张三', age: 18 }; const a = getValue(obj1, 'hh');
10.4 typeof与keyof 一起使用
function getAttri (obj:object,key:string){
// 如果 obj[key] 这种不明确obj有没有key这个类型,所以会报错
return obj[key as keyof typeof obj]
}
const a={name:'hao',age:28}
getAttri(hd,'name')
10.5 索引查询类型
作用:用来查询属性的类型
-
Props['a']表示查询类型Props中属性 ’a‘对应的类型 number,所以 TypeA的类型为number
-
注意:[]中的属性
必须存在
于被查询类型中,否则就会报错type Props={a:number; b:string; c:boolean} type TypeA=Props['a'] //type TypeA=number
-
同时查询多个索引的类型
type Props={a:number;b:string,c:boolean} // 使用字符串字面量的联合类型,获取属性 a 和 b 对应的类型,结果为:string|number type TypeA=Props['a':'b']//string|number //使用keyof操作符 获取 Props中所有键对应的类型,结果为 string| number |boolean type TypeA=Props[keyof Props] //string| number |boolean
11 declare
declare
是用于声明形式存在的
`declare var/let/const`用来声明全局的变量。
`declare function` 用来声明全局方法(函数)
`declare class` 用来声明全局类
`declare namespace` 用来声明命名空间
`declare module` 用来声明模块
12 类型声明文件
12.1 ts中的两种声明类型文件
.ts
文件
- 既
包含类型信息
又可执行代码
- 可以被编译 .js文件,然后,执行代码
- 用途:编写程序代码的地方
.d.ts
文件
只包含类型信息
的类型声明文件- 不会生成js文件,仅用于提供类型信息
- 用途:为JS提供类型信息
总结
.ts
(代码实现文件).d.ts
是(类型声明文件)- 如果要为 JS 库提供类型信息,要使用
.d.ts
文件
12.2 内置类型声明文件
-
TS为 JS 运行时可用的所有标准化内置Api都提供了声明文件
-
比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息
const strs = ['a', 'b', 'c'] // 鼠标放在 forEach 上查看类型 strs.forEach
-
实际上这都是 TS 提供的内置类型声明文件
-
可以通过 Ctrl + 鼠标左键(Mac:Command + 鼠标左键)来查看内置类型声明文件内容
-
比如,查看 forEach 方法的类型声明,在 VSCode 中会自动跳转到
lib.es5.d.ts
类型声明文件中 -
当然,像 window、document 等 BOM、DOM API 也都有相应的类型声明(
lib.dom.d.ts
)
12.3 第三方库类型声明文件
🥭说明
-
目前,几乎所有常用的第三方库都有相应的类型声明文件
-
第三方库的类型声明文件有两种存在形式:
- 库自带类型声明文件
- 由 DefinitelyTyped 提供。
🍋 库自带类型声明文件
-
比如:axios
-
查看
node_modules/axios
目录 -
过程:正常导入该库时,TS 就会自动加载库自己的类型声明文件,以提供该库的类型声明。
🍎由 DefinitelyTyped 提供
-
DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
-
可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:
@types/*
-
比如,@types/node、@types/jquery等
-
在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示
解释:当安装@types/*
类型声明包后,TS也会自动加载该类声明包,以提供该库的类型声明。
补充:TS官方文档提供了一个页面,可以来查询@types/*库。
12.4 自定义类型声明文件
说明
-
自定义类型声明文件有两个作用:
- 类型共享
- 为已有 JS 文件提供类型声明
类型共享
-
作用:
- 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享。
-
操作步骤:
- 创建 index.d.ts 类型声明文件。
- 创建需要共享的类型,并使用 export 导出(TS 中的类型也可以使用 import/export 实现模块化功能)。
- 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。
为已有 JS 文件提供类型声明
-
作用:
- 在将 JS 项目迁移到 TS 项目时,为了让已有的 .js 文件有类型声明。
- 成为库作者,创建库给其他人使用。
-
演示:基于最新的 ESModule(import/export)来为已有 .js 文件,创建类型声明文件。
-
补充说明
-
TS 项目中也可以使用 .js 文件。
-
在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以提供类型声明。
-
declare 关键字:
- 用于类型声明,为其他地方(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量。
- 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
- 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。
-
-
原始文件
let count = 10 let songName = '痴心绝对' let position = { x: 0, y: 0 } function add(x, y) { return x + y } function changeDirection(direction) { console.log(direction) } const fomartPoint = point => { console.log('当前坐标:', point) } export { count, songName, position, add, changeDirection, fomartPoint }
-
定义类型声明文件
declare let count:number declare let songName: string interface Position { x: number, y: number } declare let position: Position declare function add (x :number, y: number) : number type Direction = 'left' | 'right' | 'top' | 'bottom' declare function changeDirection (direction: Direction): void type FomartPoint = (point: Position) => void declare const fomartPoint: FomartPoint export { count, songName, position, add, changeDirection, FomartPoint, fomartPoint }
12.5 lodash- 配合 TS 使用
在ts 中使用 lodash 会报错需要安装 ts 声明 文件库
- 安装 npm install lodash -s
- 安装 声明文件库 npm install @types/lodash -D
12.6 Axios - 配合 ts 使用
目标:掌握 axios 配合 ts 使用的方式 频道接口:geek.itheima.net/v1_0/channe…
复习 axios 的使用
import axios from 'axios'
async function getData () {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
console.log(res.data.data.channels)
}
getData()
配合 ts 使用
-
当 axios 结合 ts 使用时,需要给返回的数据添加数据类型
import axios from 'axios' type Channel = { id: number name: string } type ChannelRes = { data: { channels: Channel[] }, message: string } async function getData () { const res = await axios.get<ChannelRes>('http://geek.itheima.net/v1_0/channels') console.log(res.data.data.channels) } getData()
-
保存数据,将数据渲染到页面上
<script setup lang="ts"> import { ref } from 'vue' import axios from 'axios' type Channel = { id: number name: string } type ChannelRes = { data: { channels: Channel[] }, message: string } let list = ref<Channel[]>([]) async function getData () { const res = await axios.get<ChannelRes>('http://geek.itheima.net/v1_0/channels') list.value = res.data.data.channels } getData() </script> <template> <ul> <li v-for="item in list" :key="item.id">{{ item.name }}</li> </ul> </template>
注意
axios.get() / axios.post() / axios.put() ...
可以直接通过泛型来设置返回数据的类型axios()
无法直接通过泛型设置返回数据类型
13 使用TS扩展
可以通过扩展 RouteMeta
接口来输入 meta
字段
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
//是否可选的
title?: string
//每个路由都必须声明
requiresAuth:boolean
}
}
技巧:可以通过Ctr+鼠标左键 (Mac;option+鼠标左键)来查看具体的类型信息。
14 d.ts 实现类型共享
-
创建需要共享的类型,并使用export 导出(TS中的类型也可以使用 import/实现模块化功能)
-
在需要使用共享类型的 .ts文件中,通过import 导入即可(.d.ts 后缀导入时,直接省略)
创建
index.d.ts
//商品信息 export interface Good{ id:string, name:string, price:number }
引用
import type {Good} from './data' type ResGood={ code:string, items:Good[] }
🍊 接到新项目,需要用 vue3 让我瞅瞅有什么新特性?
🍉 基于 pinia 封装持久化插件(ts版)
🍋 TS+vue3 如何结合打套组合拳
转载自:https://juejin.cn/post/7135337572951130119