TypeScript 类型系统
从基础类型进入联合、泛型、映射与条件类型,建立“用类型表达约束”的思维。
⚽ 提示
js类型 缺失,ts作为js的超集,用于类型检查,提升开发效率和代码安全性,能及早发现错误
tsconfig 配置文件
.json .js .ts 三种文件格式都可以识别
🦄 提示
作用:
- 编译时选项,tsc 或者webpack中的ts-loader (实际在编译时使用的babel,这个文件不会影响)
- 代码编辑器的提示
{
"compilerOptions": {},
"files":[],//用于指定编译的文件数组,文件较少时使用
"include":['src/**/*.ts'],//包含的文件
"exclude":[],//优先于include,排除的文件
}
环境搭建
pnpm add typescript -g
pnpm add ts-node @type/node
类型推导
🎁 提示
声明变量的时候,直接赋值,会根据赋值的类型推导出标识符的类型注解,称为类型推导
- let 声明的变量类型推导会是通用类型
- const 声明的变量类型推导,推导出来的是字面量类型
type 数据类型
📌 提示
ts中有一些预先定义的类型 如 Iterable 可迭代类型
❗ 提示
注意! ts 中number小写,是指number类型
- number 小写,ts 中的类型 js 中Number 是一个类,是用来创建实例的
- Number 大写js 中的数据类 ,ts 中也可以用
🏆 提示
**类型推断:**ts 会自动推断变量的类型,如果定义时没有赋值,不管之后有没有赋值,都会被推断成 any 类型,会报错
布尔类型
let isEnd: boolean = false;
// isEnd只能是布尔类型,不能是其他类型
数字类型
let num: number = 2;
字符串类型
let str: string = "1";
数组类型
let arr: string[] = ["1"];
// 数组中只能放字符串
let arr1: Array<number> = [1, 2, 3];
// 写法2,很少用
void 类型
function getData(a: string | number, b: string): void {
console.log("void");
}
// void类型的变量只能赋值为undefined和null 没有返回值的函数
//一个函数没有返回值时,返回值是void
//通常使用在定义函数的数据类型的地方使用
type func:()=> void
//当基于上下文类型推导中的函数,推导出来的函数返回值是viod时,如果返回一个值,并不会报错
对象类型
let obj:{name:string} = {
name: 123,
};
// 直接写,利用类型推导,不要直接这样写
元组类型
let tuple: [string, number] = ["1", 2];
// 元组类型注意注解的格式,同时必须对应赋值,注意写法,类型写在中括号中
// 函数中使用元组最多,如react中使用useState
any 类型
let mar: any = 1;
// 任意类型,当无法确定类型的时候,或者后面可能会发生改变的时候,使用该类型
//不要滥用any类型,回到js
//做任何操作都是合法的
undefined 类型
// 只有一个值就是undefined
unknown 类型
// 不确定的类型
//和any 的类型的相似,区别是的,unknown类型上做任何操作都是非法的,需要先进行类型缩小,再进行对应操作
never 类型
// 永远不能赋值的类型
//用来先设置,或者函数中有死循环,或者函数中有报错,都会报错
//一般自己不会主动定义,封装工具库时会使用
handleMessage(msg:string|number){
switch(typeof msg){
case 'String':
console.log(msg.length)
case 'Number':
console.log(msg)
default:
const check:never = msg
}
}
//一旦需要扩展handleMessage方法时,添加了类型,而忘记添加实现,则会报错,增加代码的严谨性
枚举类型
//重要
// type Direction = "left" | "Right" | "Top" | "Bottom"
//LEFT=0 RIGHT=1 TOP=2 BOTTOM=3 这里是有默认值的,实际会对应到一个数值,并且可以赋值
enum Direction {
LEFT, //LEFT=1,后面可以递增
RIGHT,
TOP,
BOTTOM,
}
function turnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
console.log("改变角色的方向向左");
break;
case Direction.RIGHT:
console.log("改变角色的方向向右");
break;
case Direction.TOP:
console.log("改变角色的方向向上");
break;
case Direction.BOTTOM:
console.log("改变角色的方向向下");
break;
default:
const foo: never = direction;
// never的典型应用 当Direction 新增选项而turnDirection没有增加对应代码的时候,会报错
break;
}
}
turnDirection(Direction.LEFT);
turnDirection(Direction.RIGHT);
turnDirection(Direction.TOP);
turnDirection(Direction.BOTTOM);
null 类型
// 只有一个值就是null
函数参数的类型
function getData(num: number, str: string): string {
return num + str;
}
// 函数参数的类型 会做提示
// 作为参数的匿名函数可以不做注解
// 函数的参数对象类型
function getData(obj: { num: number; str?: string }): string {
console.log(obj.str);
return obj.num + "";
}
//可选参数的类型,是一个联合类型 原本的类型和 undefined
//默认参数的类型也是联合类型,因为调用的时候也可以传入undefined
//剩余参数的类型注解需要写成数组
联合类型 和交叉类型
// 用竖线隔开,并集,联合类型,满足其中一种类型即可
number | string | boolean;
//交叉类型 多个类型都需要满足
interface IPerson {
name: string
}
interface IStudent{
age:number
}
//既需要满足IPerson 也需要满足IStudent
type Ikun = IPerson & IStudent
可选类型
// 参数即为可选
function(message?:string){
}
字符串字面量类型
let myName: "Tom" = "Tom";
type EventNames = "click" | "scroll" | "mousemove";
let event = "click";
// 意义在于结合联合类型才有意义
// 字面量推理
type Request = {
url: string;
method: Method;
};
type Method = "GET" | "POST";
const options: Request = {
// 方法一
url: "http://baidu.com",
method: "POST",
} as const; //方法二
// as const 会将options的属性变成只读属性,否则method是字面量类型,option里的method是string类型,
//会报错
function getData(url: string, method: Method) {}
getData(options.url, options.method);
函数类型
//ts对传入的函数参数个数不校验!!!,能少不能多
//函数本身也是一个标识符,自身也有类型
//方案一、函数类型表达式(arg:string)=> void 没有返回值
type addFn = (num:sting,str:number) => void
const add:addFn = function(num:sting,str:number){
return num+str
}
//方案二、函数调用签名 (从对象的角度看函数),当需要描述函数其他属性时,使用
interface IFunc {
name:string
(num:number):void //调用签名
new ():Person //构造签名
}
const printNumber:IFunc = function(num:number){
}
Class 类
类的继承
❗ 提示
子类可以继承父类中的属性和方法,减少相同代码的重复编写 js/ts中是单继承,只能继承单一父类
类的多态:
父类引用指向子类对象
//亦即父类作为子类的实例的类型,这样可以提前确定好子类实例中拥有哪些方法,
//让中间层可以调用,中间层不再关注具体的实现,只需要调用对应的方法即可
const square: Shape = new Square(10); //见抽象类中的具体实现
类的封装
❗ 提示
紧凑的组织代码,
类成员修饰符
成员修饰符
- public 公开访问 default
- private 私有属性 子类和实例不能访问
- protected 保护属性 子类可以访问( 使用),子类实例不能访问
- readonly 只能在声明时或构造函数里被初始化,其他地方不能修改
- static 静态属性,类可以访问,实例不能访问
❗ 提示
当构造函数修饰为
protected时,该类只允许被继承: 当构造函数修饰为private时,该类不允许被继承或者实例化
抽象类
❤️ 提示
抽象类不能被实例化,只能被继承 抽象类中的抽象方法必须被子类实现 抽象类中的抽象方法可以没有具体实现,也可以有具体实现
// 抽象类
abstract class Shape {
abstract getAera(): number;
}
class Circle extends Shape {
private r: number;
constructor(radius: number) {
super();
this.r = radius;
}
getAera() {
return Math.PI * this.r ** 2;
}
}
class Square extends Shape {
private a: number;
constructor(a: number) {
super();
this.a = a;
}
getAera() {
return this.a ** 2;
}
}
function makeAera(a: Shape) {
return a.getAera();
}
const circle = new Circle(10);
const square: Shape = new Square(10);
console.log(makeAera(circle));
console.log(makeAera(square));
类的访问器 getter /setter
❤️ 提示
访问器是一种特殊的方法,用来读取或设置某个属性的值 访问器不会被编译到 js 中,只会在编译阶段进行类型检查 访问器不必须有 get 和 set 方法,可以只有 get 方法,也可以只有 set 方法,一般用于拦截私有属性的操作 只实现get 方法,那么该属性为 readonly 访问器的参数不能有修饰符,也不能有默认值
参数属性(TS语法糖)
❗ 提示
在constructor方法中,参数前面加上修饰符,即可简化属性的编写
class Person {
constructor(public name: string, public age: number) {
}
}
const p1 = new Person('Max', 30);
console.log(p1.name);
类的类型特性
❗ 提示
- 类可以创建实例
- 类可以作为实例的类型
- 类可以作为有构造签名的函数- 工厂函数中使用
interface 接口
❗ 提示
主要用于声明对象类型,接口中的属性和方法都是抽象的,不需要具体实现
interface Itable {
head: string;
body: string;
foot?: string; //可选属性
content?: {
name: string;
age: number;
};
}
// 键值对的定义方式 内置的Record
interface IndexItem {
[index: string]: string;
}
// 函数类型的接口
interface IAdd {
(a: number, b: number): number;
}
接口的继承
❗ 提示
接口可以继承多个接口
interface ISwim {
swimming: () = void;
}
interface IFly {
flying: () = void;
}
interface IAction extends ISwim, IFly {}
const action: IAction = {
swimming() {},
flying() {},
};
接口的合并
// 一种组合类型的方式: 联合类型
type WhyType = number | string;
type Direction = "left" | "right" | "center";
// 另一种组件类型的方式: 交叉类型
type WType = number & string;
interface ISwim {
swimming: () = void;
}
interface IFly {
flying: () = void;
}
type MyType1 = ISwim | IFly;
type MyType2 = ISwim & IFly;
const obj1: MyType1 = {
flying() {},
};
const obj2: MyType2 = {
swimming() {},
flying() {},
};
接口的实现
interface ISwim {
swimming: () = void;
}
interface IEat {
eating: () = void;
}
// 类实现接口
class Animal {}
// 继承: js只能实现单继承
// 实现: 实现接口, 类可以实现多个接口 -类实现 可以比接口定义的属性多
class Fish extends Animal implements ISwim, IEat {
swimming() {
console.log("Fish Swmming");
}
eating() {
console.log("Fish Eating");
}
}
class Person<T implements ISwim<T {
// 可传泛型
swimming() {
console.log("Person Swimming");
}
}
// 编写一些公共的API: 面向接口编程
function swimAction(swimable: ISwim) {
swimable.swimming();
}
// 1.所有实现了接口的类对应的对象, 都是可以传入
swimAction(new Fish());
swimAction(new Person());
swimAction({ swimming: function () {} });
interface 和 type 的区别
| 继承接口 | 定义多个相同 | 实现接口 | 定义函数类型 | 声明基本类型 | |
|---|---|---|---|---|---|
| type | ❎ | ❎ | ❎ | ❎ | ✅ |
| interface | ✅ | ✅ 内容会合并 | ✅ | ✅ | ❎ |
❤️ 提示
建议基本类型使用type,对象类型使用interface
泛型
🥖 提示
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用一个变量代替,而在使用的时候再指定类型的一种特性 泛型的思想就是把类型抽象成一个变量,在定义函数或者类的时候,传入类型的形参,使用的时候传入或根据参数推导具体的类型

🌟 提示
泛型的类型限制是通过接口的继承实现的
// 首先定义一个具有长度的类型 接口
interface ILength {
length: number;
}
// 函数传入的类型形参是继承自ILength的类型,则必需实现了length属性,否则会报错
// 泛型约束
function getLength<T extends ILength(arg: T): number {
return arg.length;
// 而且传入的参数类型必须是ILength的子类 ,所以肯定具有length属性
}
// 所以可见,使用泛型具有通用的约束参数类型的作用
// 传入的参数类型也可以是多个或者有默认值的
function getLength2<T extends ILength, K = string>(arg1: T, arg2: K): number {
return arg1.length + arg2.length;
}
// 调用函数可以不直接写具体的传入类型,函数能够自动推导出类型
getLength2("abc", "def");
泛型约束
- 简单约束
interface ILength {
length: number;
}
// 函数传入的类型形参是继承自ILength的类型,则必需实现了length属性,否则会报错
function getLength<T extends ILength(arg: T): number {
return arg.length;
// 而且传入的参数类型必须是ILength的子类 ,所以肯定具有length属性
}
- keyof 用来约束类型必须是对象中的一个key
interface IPerson {
name:string
age: number
height:string
}
type PersonKeys = keyof IPerson // 'name' | 'age' | 'height'
//约束K必须是IPerson当中的一个key
function getLength<T extends IPerson,K extends keyof IPerson >(arg: T,arg2:K): number {
return arg.age;
}
- 映射类型 - 使用对象索引签名实现对一个接口的复制,并且对其修改
interface IPerson {
readonly name: string;
age: number;
height: string;
}
//可以使用+ - 来增加或者去除原来类型中的修饰符
// 使用的索引签名 和 类型提取实现类型复制
//需用type
type MapIPerson<T> = {
- readonly [prop in keyof T]-?: T[prop]
}
type NewPerson = MapIPerson<IPerson>
//即可复制IPerson的属性,需用type
ts知识扩展
类型断言
- as 定义为某一个类型,如果需要强制转换,需要先转换成unknown 再转换成所需类型
- ! 非空断言 值不为空
//1. as关键词
// 用来告诉ts变量的实际类型
//类型断言只能把类型断言成更加(从不太具体的类型断言成)具体,或者(从比较的具体的类型断言成)更加不太具体的类型
class Person{}
class Student extends Person{
studying() {}
}
function sayHello(p:Person) {
(p as Student).studying()
// 把p断言为子类,就可以调用子类的方法
}
let stu=new Student();
sayHello(stu)
//2. 非空断言 !
function sayHello(name?: string) {
//非空断言,保证非空,调用方法
console.log( name!.length);
}
// 3.扩展
// 1. 可选链 ?. 用来判断是否有值
// 2. !! 转换成布尔值
// 3. ?? 用来判断是否有值,有值就返回,没有值就返回后面的值,比三目运算符更加方便
类型别名
type myType = number | string | boolean;
// 使用type来定义类型别名
type myObjType = {
name: string;
age: number;
height?: number;
};
// 对象别名也可以是对象
类型缩小
❗ 提示
缩小类型的范围,从而可以使用特定类型的方法
- typeof / instanceof / in
- === !== 平等缩小
//亦即在(联合类型)变量使用的时候,多一层判断,确认最终类型
type IDtype = string |number
function getData(id:IDtype){
if(typeof id === 'string'){
//此时id就是string类型
console.log(id.length)
}
}
// in的判断方式
type Dog = {
bark(){}
}
type Cat = {
Miao(){ }
}
function sound(p:Cat|Dog){
// 通过in来判断
if('bark' in p){
p.bark()
}else{
p.Miao()
}
}
const pet:Dog= {
bark(){
console.log('汪汪汪')
}
}
sound(pet)
函数的重载
// 定义相同名称,参数不同的几个函数签名,只有一个实现函数,ts会根据参数的类型 来判断调用哪个函数
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
return a + b;
}
console.log(add(10, 20));
console.log(add("10", "20"));
//实际
//能通过联合类型实现的优先使用联合类型,不能实现的才使用函数重载,比较麻烦
鸭子类型
❗ 提示
一个对象看起来像鸭子,叫声像鸭子,行为像鸭子,那么可以认为它是一个鸭子
ts中的类型检测使用的鸭子类型
❤️ 提示
鸭子类型是一种动态类型的概念,它是指在运行时,一个对象的有效语义是由它当前方法和属性的集合决定的,而不是由它所继承的特定类或接口的集合决定的。这意味着,只要一个对象具有与所需方法和属性相同的方法和属性,它就可以被视为是所需的类型。这种类型检查方式通常用于动态语言中,如JavaScript和Python
对象和接口中的属性都可以使用的修饰符
?.可选Readonly只读
对象接口索引签名
相当于一个动态属性
type Person = {
[index: number]: string //允许通过索引获取值
lenght: number
}
interface Person {
[index: number]: string; //允许通过索引获取值
lenght: number;
[key:string]:string|number //[]中的类型只能是string 或者number ,不能是其他,联合也不行
};
//1. 可以使用2个索引签名的时候, [key:string]返回的类型必要是[index: number]返回的类型的父类型
//因为number类型最终会转换成string
//2. 其他属性( lenght)也必须符合[key:string]返回的类型
//其实就是一条:其他属性( 包含[index: number])也必须符合[key:string]返回的类型的子类型
字面量赋值
严格对象字面量赋值
在使用字面量对接口或者类型赋值时,直接赋值会严格校验字面量的属性必须和接口保持一致,如下例子
interface IPerson {
name: string;
age: number;
height: number;
}
const info = {
name: "why",
age: 18,
height: 1.88,
address: "广州市"
}
const p2 : IPerson ={
name: "why",
age: 18,
height: 1.88,
address: "广州市"
}
//这样会报错
const p: IPerson = info
// freshness擦除 类型检测时
//这样不会报错
// console.log(info)
// console.log(p)
function printInfo(person: IPerson) {
console.log(person);
}
// 代码会报错
printInfo({
name: "why",
age: 18,
height: 1.88,
address: "广州市"
})
const info = {
name: "why",
age: 18,
height: 1.88,
address: "广州市",
};
// 使用对象的引用,会在类型检测的时候有擦除,所以不会报错
printInfo(info);
ts的知识扩展
- 模块化 支持各种模块化 最推荐的模块化方式是esm
❗ 提示
- js规范声明任何没有export的js文件都会认为是一个脚本,而非一个模块
- 在一个脚本文件中,变量和类型会被声明在共享的全局作用域,将多个输入文件合并成一个输出文件
- 如果希望它被作为一个模块处理,需要添加
export {}
这会把文件当成单独的模块处理,有独立的作用域,不会污染作用域
- 类型的导入导出
//方法一
import type { IPerson } from './type'
//方法二
import { type IPerson } from './type'
//type.ts
export interface IPerson {
name:string
}
✍️ 提示
使用type关键词的好处,使用babel、esbuild等编译时,方便识别安全移除,提升效率 不使用也可以
- 类型查找 类型声明
🎉 提示
.d.ts文件,主要用于类型声明,不写代码逻辑
- 内部类型声明 安装typescript后
- 外部类型声明 自定义代码类型声明
- 第三方库类型声明
//声明变量
declare const age:number
//声明模块
declare module 'lodash '{
//模块内部需要导出
export function join(...args:any[]):any
}
//声明静态资源
declare module '*.jpg'
ts内置工具
interface IFunc {
name: string;
(this: { name: string }, num: number): void;
}
const printNumber: IFunc = function (this, num) {
console.log(num);
};
printNumber.name = "print";
//获取函数的类型
type printType = typeof printNumber;
//ThisParameterType获取这个类型的this
type This = ThisParameterType<printType>;
//提取除了this以外的类型
type pureType = OmitThisParameter<printType>;
//绑定上下文的方法
ThisType<printType>;