TypeScript w字入门文档

526 阅读16分钟

TypeScript

本篇文章学习源:禹神:三小时快速上手TypeScript 又是摸鱼的一天,不行不能在这样了 >.<!!

虽然并不是没有事情、只是并不能在单位明晃晃的:刷题、不然会被烦 然后,就会有任务的了,年底了都是这样吧

趁这个机会更新一下技术栈吧: 作为一个后端程序员,对于前端也可以达到简单使用的程度了,也正因如此:

  • 原生 JavaScript 是弱语言,带来了一些方便同时也存在,很多痛苦;校验类型弱,没有提示;

  • 虽然部分可以通过、编辑工具的插件来进行扩展、代码提示,对小型项目一定程度很方便

  • 但——对大型项目团队合作: 存在的诸多问题: 没有静态类型检查、类、接口、模块;

    在编写代码时,变量类型的不确定性使得代码中很容易潜藏一些不易察觉的类型错误;

    团队开发过程中,对一个函数没办法明确知道需要传递、接受的参数... 协作成本;

且、对于本身学过:Java、C#: 对于TypeScript 的理解上手可以说,水到渠成;

什么是Typescript?

TypeScript简称TS: TSJS之间的关系其实就是Less/SassCSS之间的关系,

TypeScript 由微软开发,基于 JavaScript 的⼀个扩展语⾔,包含了 JavaScript 的所有内容

TypeScript 增加了: 静态类型检查、接⼝、 泛型等很多现代开发特性,更适合 ⼤型项⽬的开发;

JavaScript 的缺点:

JavaScript 当年诞⽣时的定位是浏览器脚本语⾔: ⽤于在⽹⻚中嵌⼊简单的逻辑,且代码量很少;

  • 随着时间的推移,JavaScript 变得越来越流⾏,如今的 JavaScript 已经可以全栈编程了
  • 然⽽ JavaScript 当年 “出⽣简陋”,逐渐就出现了很多困扰:

不清楚的数据类型:

由于变量的数据类型可以在运行时动态改变,这导致一些在编译时难以发现的类型错误,

只有在程序运行到特定代码行时才会暴露出来,增加了调试的难度和成本;

let num = 10;
num = "Hello"; 
console.log(num * 2); 
// 变量类型可随意改变,可能导致意外结果
// 输出结果为NaN,因为此时num已经是字符串,不能进行数学运算

// 函数参数类型不确定
function add(a, b) {
    return a + b;
}
console.log(add(5, 10)); 			// 正常情况,输出15
console.log(add("5", "10")); 	// 输出 "510",并非预期的数字相加结果

访问不存在的属性: 浏览器兼容性问题、 访问不存在的属性时,通常会返回undefined,并且不会引发错误;

原型继承的理解难度: 因为弱语言,JavaScript 创建对象的方式很复杂、非常不方便理解和管理;

先比之下: TypeScript:更适合大型项目的开发,特别是在团队协作和代码维护方面具有优势;

它在前端框架如 React、Angular 中得到了广泛应用,能够帮助开发者更好地组织代码、提高代码的可读性和可维护性

TypeScript:由于其增加了类型系统和一些新的概念,对于初学者来说可能会有一定的学习难度,能够更高效地进行开发和代码维护

『静态类型检查』

TypeScript 和核⼼就是『静态类型检查』,简⾔之就是把运⾏时的 错误前置:

在代码运⾏前进⾏检查,发现代码的错误或不合理之处,减⼩运⾏时出现异常的⼏率,此种检 查叫**『静态类型检查』**

同样的功能,TypeScript 的代码量要⼤于 JavaScript,但由于 TypeScript 的代码结构更加 清晰,

在后期代码的维护中 TypeScript 却胜于 JavaScript,适合于大型团队开发项目;

TypeScript 使用:

浏览器不能直接运行 TypeScript 代码,需要编译为 JavaScript 再交由浏览器解析器执行:

全局安装 TypeScript: npm i typescript -g 、命令编译 .ts ⽂件:tsc

命令行编译:

了解即可: 首先在项目中编写 demo.ts 文件,之后通过命令编译 tsc xxx.ts ⽂件 xxx.js

const person = { name: '李四', age: 18 };
console.log(`我叫${person.name},我今年${person.age}岁了`);

在这里插入图片描述

自动化编译:

🆗,上面已经通过: tsc 进行TS==> JS的操作,实际开发过程中一定不会频繁的tsc通常使用:

tsc --init
# 创建TypeScript 编译控制⽂件
# ⼯程中会⽣成⼀个tsconfig.json 配置⽂件,其中包含着很多编译时的配置


tsc --watch  或 tsc -w				# 监视⽬录中的.ts ⽂件变化
tsc --noEmitOnError --watch	 # 当编译出错时不⽣成 .js ⽂件 或在配置文件进行配置;

在这里插入图片描述

类型声明:

TypeScript 的类型声明是其核心特性之一,它允许开发者显式地指定变量、函数参数、函数返回值、对象属性等的类型;

使⽤ : 来对变量或函数形参,进行类型声明:

let a: string   //变量a只能存储字符串
let b: number   //变量b只能存储数值
let c: boolean  //变量c只能存储布尔值
a = 'hello'
b = 6666
c = true
//a = 100         //警告:不能将类型"number"分配给类型"string"
//b ='你好'       //警告:不能将类型"string"分配给类型"number"

//参数x必须是数字,参数y也必须是数字,函数返回值也必须是数字
function demo(x: number, y: number): number {
    return x + y
}
demo(100, 200)

//demo(100)               //警告:应有2个参数,但获得1个
//demo(100, '200')        //警告:类型"string"的参数不能赋给类型“number"的参数

在这里插入图片描述

注意事项:

基本数据类型—包装类—数据类型:

JavaScript 内置构造函数:Number、String、Boolean ,用于创建对应的包装对象,在日常开发时很少使用,

在 TypeScript 中也是同理,所以在 TypeScript 中进行类型声明时,通常都是用小写的 number、string、boolean

//基本数据类型—包装类—数据类型:
let str1: string
str1 = 'hello str1'
// str1 = new String('hello')  //报错
// TypeScript 中变量被定义为string这种基本数据类型时,它只能接收基本字符串值
// 而不能接收由String构造函数创建出来的包装对象类型的值, 虽然在 JavaScript 自动的装箱、拆箱

//包装类对象:TypeScript 会进行隐式类型转换
let str2: String
str2 = 'hello str2'
str2 = new String('x')
console.log(typeof str1)
console.log(typeof str2)

原始类型 VS 包装对象: 原始类型:JavaScript 中是简单数据类型,它们在内存中占用空间少,处理速度快

  • 包装对象:是复杂类型,在内存中占用更多空间,在日常开发时很少由开发人员自己创建包装对象
  • ⾃动装箱:JavaScript 在必要时会⾃动将原始类型包装成对象
// 原始类型字符串
let str='hello';
//当访问str.length时,JavaScript引擎做了以下工作:
let size =(function(){
  let tempStringObject = new String(str);			//1.自动装箱:创建一个临时的String对象包装原始字符串
  let lengthValue = tempStringObject.length;	//2.访问String对象的length属性
	//3.销毁临时对象,返回长度值(JavaScript引擎自动处理对象销毁,开发者无感知)
  return lengthValue;
})();
console.log(size);//输出:5

类型推断:

TS 会根据我们的代码,进⾏类型推导,例如:下⾯代码中的变量 let d = -99则该变量只能存储数字;

let d = -999		//TypeScript会推断出变量d的类型是数字
d = false				//警告: 不能将类型"boolean"分配给类型"number”
//类型推断不是万能的,面对复杂类型时推断容易出问题,所以尽量还是明确的编写类型声明!

类型总览:

JavaScript 的基本数据类型: Number、String、Boolean、null、undeined

  • null:表示一个空值或不存在的对象引用
  • undefined:表示变量未定义或未初始化的值

复杂数据类型: Object 对象、Array 数组、Function 函数 ...等 Date Error)

  • Array:用于存储一组有序的数据,数组中的元素可以是任何类型

  • Object:是 JavaScript 中最常用的数据类型之一,它可以包含多个键值对,用于表示复杂的数据结构

  • Function:函数在 JavaScript 中也是一种数据类型,可以作为变量赋值、作为参数传递给其他函数或作为函数的返回值

TypeScript 数据类型: 包含JavaScript 所有的数据类型,之外引入了新的数据类型

  • any、unknown、never、void、tuple、enum
  • 自定义类型: type、interface

TypeScirpt 常用数据类型

any

灵活性高any 类型可以表示任意类型的值,包括基本数据类型,如numberstringboolean 对象、数组、函数等

这使得在编写代码时可以 绕过 TypeScript 的类型检查系统,给变量赋予任何类型的值,例如:

let value: any;
value = 10;
value = "Hello";
value = true;
value = { name: "John" };
value = [1, 2, 3];
value = () => console.log("Function");

失去类型安全性:由于any类型可以接受任何值,所以在使用any类型的变量时,TypeScript 不会对其进行类型检查;

这可能导致在运行时:出现意外的类型错误,因为在编译阶段无法发现潜在的类型不匹配问题

let value2: any = 10;
let result: string = value;     //这里不会报错,但在运行时可能会出现问题;

所以谨慎使用: 动态类型编程:在某些情况下,需要编写具有高度动态性的代码,

例如根据用户输入或运行时条件动态地改变变量的类型,可以使用any类型来实现更灵活的编程

  • 第三方库的使用: 当使用一些没有提供 TypeScript 类型定义的第三方库时,需要使用any类型来与这些库进行交互;
  • 替代方案: unknown 类型:与any类型类似,unknown类型也可以表示任意类型的值,但它比any类型更安全;

unknown

unknown类型是一种安全的顶级类型,用于表示未知类型的值

any类型不同,unknown类型在 使用前:必须进行类型检查或类型断言,这使得代码更加安全

可赋值给任何类型unknown类型的值可以赋值给任何其他类型的变量,但使用需要进行类型断言或类型检查

let unk: unknown
unk = 100
unk = false
unk = '你好,世界'

//x = unk 报错: “unknown”直接分配给类型string 
let x: string
//1. 需要进行类型判断
if (typeof unk === 'string') { x = unk }
//2. 断言进行类型判断
x = unk as string;  //as 断言类型转换
x = <string>unk;    //as 断言类型转换

与 any 类型的区别:

  • 类型检查any类型可以在不进行任何类型检查的情况下进行任意操作,而unknown类型在操作之前必须进行类型检查或类型断言
  • 赋值限制unknown类型只能赋值给unknown类型或any类型的变量,而any类型可以赋值给任何类型的变量
  • 安全性unknown类型提供了更高的类型安全性,有助于在编译阶段发现潜在的类型错误

never

never类型表示那些永远不会发生的值的类型,目前没搞懂使用,且用处不多

无值类型never类型是一个底层类型,

它没有任何值,是所有类型的子类型,它表示一种理论上永远不会存在的值的类型

  • 这意味着 任何类型都可以赋值给never类型的变量,但never类型的值永远不会出现在实际的代码运行中
  • 不可赋值性:尽管never可以被赋值给任何类型,但反过来,没有任何类型(除了never自身)可以赋值给never类型的变量

类型推断:在某些特定的情况下,TypeScript 会自动推断出never类型,比如:在函数中抛出异常或者函数永远不会返回的情况下

/*指定的never类型 那就意味着以后不能存任何的数据了 */
let nev: never
// 以下对a的所有赋值都会有警告
// nev =1
// nev = true
// nev = undefined

//作为函数返回值: 是never 常用的情况
//函数总是抛出异常:当一个函数总是抛出异常,而不会有正常的返回值时,它的返回值类型可以声明为never
function throwError(message: string): never {
    throw new Error(message);
}

//函数永不返回:如果一个函数进入了一个无限循环或者执行了一些导致它永远无法返回的操作,那么它的返回值类型也可以是never
function infiniteLoop(): never {
    while (true) {
        // 无限循环
    }
}

类型保护中的检查:

类型推断:在某些特定的情况下,TypeScript 会自动推断出never类型,

在进行类型守卫和类型断言时,never类型可以用于确保所有可能的类型都被处理到了,避免遗漏;

在处理 switch 或多类型分支时,never 可用于强制检查是否所有可能的分支都被覆盖,帮助捕获遗漏的情况

  • 在 switch 语句的 default 块中,我们定义了一个 exhaustiveCheck 变量,类型为 never
  • TypeScript 编译检查,如果 status 不是 never 类型(即还存在未处理的情况)触发编译错误
//TypeScirpt 常用数据类型 never
//类型推断:在某些特定的情况下,TypeScript 会自动推断出`never`类型,比如:在函数中抛出异常或者函数永远不会返回的情况下
type UserStatus = "active" | "inactive" | "banned";

function handleUserStatus(status: UserStatus) {
    switch (status) {
        case "active":
            console.log("User is active");
            break;
        case "inactive":
            console.log("User is inactive");
            break;
        case "banned":
            console.log("User is banned");
            break;
        default:
            // 强制类型检查是否已全面覆盖所有情况
            const exhaustiveCheck: never = status;
            throw new Error(`Unhandled status: ${status}`);
        //如果case处理了所有可能出现的情况,则这段代码永远不会执行,如果执行则编译报错,也不会通过!!!
    }
}

在这里插入图片描述

😲😲😲🤯🤯🤯 利用TypeScript 编译检查,因为:status如果每个值都都会处理,则不可能执行default

而,理论上程序default 块代码永远不会执行,这个代码,就利用TypeScript 编译检查

确保、提醒开发者不会忽略可能出现的,逻辑漏洞!!!

这就是程序的魅力~~~

void

void是一种常用的数据类型,主要用于表示函数没有返回值或返回值为undefined的情况;

函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!

  • 编码者没有编写 return 指定函数返回值,但会有一个隐式返回值,是undefined
  • 虽然函数返回类型为 void ,但也是可以接受 undefined 的,简单记: undefined 是 void 可以接受的一种“空”
// 无警告
function logMessage(msg: string): void {
    console.log(msg)
}
// 无警告
function logMessage2(msg: string): void {
    console.log(msg)
    return;
}
// 无警告
function logMessage3(msg: string): void {
    console.log(msg)
    return undefined
}

undefined 和 void 区别:

void 函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!

//函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!
function logMessage4(msg: string): undefined {
    console.log(msg)
}

let revoid = logMessage('你好');
// if (revoid) { /** 此处报错 */  }

let revoid2 = logMessage4('你好');
if (revoid2) { /** 此处无警告 */ }
  • void 是⼀个⼴泛的概念,⽤来表达“ 空” ,⽽ undefined 则是这种**“ 空” 的具体 实现**
  • 也可以理解为: void 包含 undefined ,但 void 所表达的语义超越了 undefi ned ,
  • void 是⼀种意图上的约定,⽽不仅仅是特定值的限制

object\Object

关于 object 与 Object 实际开发中:直接使⽤的相对较少

TypeScript 中,objectObject是两个不同的类型,它们在含义和使用上有一些区别:

object 类型

object ⼩写的含义是: 所有⾮原始类型,可存储:对象、函数、数组等,由于限制 的范围⽐较宽泛;

//TypeScirpt 常用数据类型 object
let ao: object //a的值可以是任何【非原始类型】,包括:对象、函数、数组等
// 以下代码,是将【非原始类型】赋给a,所以均符合要求
ao = {}
ao = { name: '张三' }
ao = [1, 3, 5, 7, 9]
ao = function () { }
ao = new String('123')

//以下代码,是将【原始类型】赋给a,有警告
// ao = 1      // 警告:不能将类型“number”分配给类型“object”
// ao = true   // 警告:不能将类型“boolean”分配给类型“object'
// ao = '你好' // 警告:不能将类型“string”分配给类型“object”
// ao = null   // 警告:不能将类型“null”分配给类型“object”a = null
// ao = undefined // 警告:不能将类型“undefined”分配给类型“object”

和 Object 的区别:

Object ⼤写

  • 官⽅描述:所有可以调⽤ Object ⽅法的类型
  • 简单记忆:除了 undefined 和 null 的任何值。
  • 由于限制的范围实在太⼤了!所以实际开发中使⽤频率极低。
//TypeScirpt 常用数据类型 Object
let bo: Object //b的值必须是0bject的实例对象(除去undefined和null的任何值)
//以下代码,均无警告,因为给a赋的值,都是0bject的实例对象
bo = {}
bo = { name: '张三' }
bo = [1, 3, 5, 7, 9]
bo = function () { }
bo = new String('123')
bo = 1       // 1不是0bject的实例对象,但其包装对象是0bject的实例
bo = true    // truue不是0bject的实例对象,但其包装对象是0bject的实例
bo = '你好'   //“你好”不是0bject的实例对象,但其包装对象是0bject的实例

//以下代码均有警告
// bo = null       //警告:不能将类型“null”分配给类型“0bject”
// bo = undefined  // 警告:不能将类型“undefined"分配给类型“0bject'

声明对象类型:

实际开发过程中并不会直接使用:Object、object 创建对象

更多使用:声明式—字面量形式创建对象: 使用花括号{}包裹一组键值对来创建对象,键与值之间用冒号:分隔;

//TypeScirpt 字面量形式创建对象
let user: { name: string, age: number } //定义一个User类型对象;
//初始化user对象——变量, 必须严格遵循字面量属性:值;
user = {
    name: "user1",
    age: 0
}

//可选属性:字面量声明属性时 [xxxx? : 类型] +?则该属性可选
let user2: { name: string, age?: number } //定义一个User类型对象;
user2 = { name: "user2", age: 12 }
user2 = { name: "user2", }
索引签名:

允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的,常⽤于:描述类型不确定的属性,具有动态属性的对象;

//索引签名:允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的
let user3: {
    name: string,
    age?: number,
    [key: string]: any,
    //设置:属性名string类型: any  属性值any任意类型;
}
user3 = { name: "user2", age: 12, idnumber: "320324xxx" }
user3 = { name: "user2", age: 12, height: 180, weight: 90 }

声明函数类型:

TypeScript 中,可以通过字面量形式创建函数,主要有两种常见方式:声明方式很多,介绍不全面,顺手即可

  • 普通函数字面量(匿名函数表达式)
  • 箭头函数字面量

普通函数(匿名函数) 使用function关键字,后面紧跟参数列表(用小括号括起来) : 返回值类型

函数体 { 用大括号括起来,若函数体只有一行语句,大括号也可省略 } ,整体构成一个匿名函数表达式

// 简单的普通函数字面量,无参数,无返回值
let sayHello = function () {
    console.log("Hello!");
};

// 有参数和返回值的普通函数字面量
let add = function (num1: number, num2: number): number {
    return num1 + num2;
};

// 函数体只有一行语句时,省略大括号的情况(不推荐用于复杂逻辑,可读性较差)
let multiply = function (num1: number, num2: number): number { return num1 * num2; };

箭头函数字面量: 参数列表 (用小括号括起来,若只有一个参数,小括号可省略) : 返回值类型 箭头=>

以及函数体 { 用大括号括起来,若函数体只有一行语句,大括号及return关键字都可省略 }

// 箭头函数字面量:
// 无参数的箭头函数字面量,无返回值
let greet = () => { console.log("Greetings!"); };

// 有多个参数的箭头函数字面量,不省略小括号,有返回值
let sum = (num1: number, num2: number): number => { return num1 + num2; }
// 如果函数有返回值,TypeScript 会自动根据函数体的逻辑推断返回值类型,
// 但为了更明确和保证类型安全,也可以显式标注返回值类型
//调用:
greet();
const result4 = sum(3, 4);

声明数组类型:

使用类型[]的形式来声明数组类型,其中类型可以是任何有效的 TypeScript 类型

//TypeScirpt 字面量形式创建数组
//使用`类型[]`的形式来声明数组类型,其中`类型`可以是任何有效的 TypeScript 类型
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: string[] = ["apple", "banana", "cherry"];
//多维数组: 声明多维数组时,只需在基本的数组类型声明基础上,继续嵌套数组类型即可
// 二维数组
let matrix: number[][] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
// 三维数组
let cube: number[][][] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]];

// 泛型语法: 也可以使用泛型语法Array<类型>来声明数组类型,这种方式与类型[]基本等价,但在一些复杂的类型定义中可能更灵活
let numbersA: Array<number> = [1, 2, 3, 4, 5];
let stringsA: Array<string> = ["apple", "banana", "cherry"];

包含不同类型的数组: 如果数组中可能包含不同类型的元素,可以使用联合类型来声明数组类型

let mixedArray: (number | string | boolean)[] = [1, "apple", true];

只读数组: 使用ReadonlyArray<类型>类型[]加上readonly关键字可以声明只读数组,一旦声明后,数组的元素将不能被修改

let readonlyNumbers: ReadonlyArray<number> = [1, 2, 3];
let readonlyNumbers2: readonly number[] = [1, 2, 3];

tuple

TypeScript 中,元组(Tuple)是一种特殊的数据类型:

它允许你表示一个已知元素数量和类型的数组,各元素的类型可以不同。元组的长度是固定的

//第一个元素必须是 string 类型,第二个元素必须是 number 类型。
let arr1: [string, number] = ["xx", 123];

//第一个元素必须是 number 类型,第二个元素是可选的,如果存在,必须是 boolean 类型。
let arr2: [number, boolean?];
arr2 = [123];
arr2 = [123, true];
//arr2 = [123, true, "xxxx"]  //警告!!!

//第一个元素必须是 number 类型,后面的元素可以是任意数量的 string 类型let arr3: [number,...string[]]
let arr3: [number, ...string[]]
arr3 = [100]
arr3 = [200, "x", "xx"]
arr3 = [300, "x", "xx", "xxx"]

元组在函数返回多个不同类型的值时非常有用。例如,一个函数返回一个操作的结果和状态码:

//调用这个函数let [res, code] = doSomething(); 就可以方便地获取返回的结果和状态码;
function doSomething(): [string, number] {
  let result = "Success";
  let statusCode = 200;
  return [result, statusCode];
}

与数组的区别

  • 数组的长度是可变的,并且所有元素的类型通常是相同的当然,也可以是 any[]类型,但这失去了类型安全的优势
  • 元组长度固定,并且每个位置的元素类型可以不同,这使得元组在需要精确控制数据结构时非常有用

enum

enum 对于学习过后端语言的应该很熟悉==》 不用想的太复杂,它就是为开发者,开发过程中预防失误,定义的一组规范类型;

枚举(Enum)是用户定义的数据类型:,它允许你定义一组命名的常量,使用enum关键字来声明枚举:

  • 在一个表示用户状态的系统中,可能会频繁使用: 0 未激活1 已激活2 已封禁
//未使用枚举判断用户类型:
function checkUserStatus(status: number) {
  if (status === 0) {
    console.log("用户未激活");
  } else if (status === 1) {
    console.log("用户已激活");
  } else if (status === 2) {
    console.log("用户已封禁");
  }
}
//方法调用:
checkUserStatus(1);     //不容易辨别;

而开发者,并不能明确的知道需要传递的数值,对于经常使用的这些值,可以定义一组枚举进行管理;

  • 程序的异常类型enum、商品状态enum、日志级别enum ...等

数字枚举 默认)

数字枚举⼀种最常⻅的枚举类型,其成员的值会⾃动递增,且数字枚举还具备 反向映射的特点;

//1.创建枚举类
enum UStatus {
    Inactive,
    Active,
    Banned
}
//2.使用枚举判断用户类型:
function checkUserStatus(status: UStatus) {
    if (status === UStatus.Inactive) {
        console.log("用户未激活");
    } else if (status === UStatus.Active) {
        console.log("用户已激活");
    } else if (status === UStatus.Banned) {
        console.log("用户已封禁");
    }
}
//方法调用:
checkUserStatus(UStatus.Inactive);  //清晰明了,代码的可读性得到了显著提高。
// UStatus.Inactive等名称清晰地表明了这些值的含义,而不是让读者去猜测0、1、2等数字所代表的状态

反向映射: 如果你有一个枚举成员的值,你可以通过这个值获取对应的枚举成员 仅数字枚举支持)

//反向映射:如果你有一个枚举成员的值,你可以通过这个值获取对应的枚举成员
console.log(UStatus[0]);    //Inactive

字符串枚举:

字符串枚举是一种特殊的枚举类型,其中枚举成员的值是字符串:

与数字枚举不同,字符串枚举的每个成员都需要显式地赋值为一个字符串,不支持反向映射,其他都一样;

//字符串枚举: 
enum UStatus {
    Inactive = "用户未激活", 
    Active = "用户已激活",
    Banned = "用户已封禁"
}

常量枚举:

在性能敏感的场景下,可以使用常数枚举。常数枚举在编译时会被完全内联,减少了运行时的开销;

  • 它使用 const 关键字定义:,在编译时会被内联,避免生成一些额外的代码

type

type关键字用于定义自定义类型: 它允许你将一组类型组合在一起,形成一个新的、更复杂的类型

//类型别名
//type定义的实际上是一种类型别名。这意味着它为一个已有的类型或者一组类型组合提供了一个新的名称
type str = string;                  //string 别名类型
type num = number;                  //number 别名类型
type strnum = string | number;      //string类型或者number类型

let tstr1: str = "xxx";
let tnum1: num = 123456;
let tstrnum2: strnum = 123456;
let tstrnum1: strnum = "123456";    //既可以是string 也可以是 number

复杂交叉类型:

type可以用于构建非常复杂的类型: 将一组类型组合在一起,形成一个新的、更复杂的类型

//复杂交叉类型:
//例如,你可以定义用户对象类型:
type tuser = { name: string, age: number }
let tuser1: tuser = { name: "wsm", age: 18 };
//定义技能对象类型:
type tskill = { java: boolean, py: boolean, c: boolean }
let tskill1: tskill = { java: false, py: false, c: true };

//交叉类型: 许将多个类型合并为⼀个类型
type tuserskill = tuser & tskill;
let tuserskill1: tuserskill = { name: "zhanhsan", age: 188, java: true, py: true, c: true };

函数类型、特殊函数void:

使用type关键字可以精确地定义函数类型,一个函数类型包括参数类型和返回值类型:

  • 例如,定义一个接收两个数字参数并返回一个数字的函数类型:
//TypeScript 数据类型 函数类型
type AddFunctionType = (num1: number, num2: number) => number;
let addFunction: AddFunctionType = (a: number, b: number) => a + b;
//函数类型的使用:可以将定义好的函数类型用于函数变量的声明或者作为函数参数的类型

特殊函数void: 在 TypeScript 中,void类型用于表示没有返回值的函数,当一个函数,不返回任何有意义的值 void

  • 但在 type 声明函数 返回值void 时是可以有其他类型返回值的,但并不运行操作;
  • 只需要记住这个特性就可以了: 可以当作一个Bug 或一个特性;
//TypeScript 数据类型 特殊函数类型void
//在 TypeScript 中,void类型用于表示没有返回值的函数,当一个函数不返回任何有意义的值 void
type tfunv = () => void;

let tfunv: tfunv = function () { console.log("空参默认返回:undefined"); }

let tfunv1: tfunv = function () {
    console.log("空参手动返回:undefined");
    return undefined
}

let tfunv2: tfunv = function () {
    console.log("空参return str");
    return "并没有警告";
}

tfunv();
tfunv1();
let tfstr = tfunv2();
console.log("打印输入:" + tfstr);
//if (tfstr == "") { /** 但并不允许使用; */ }  //警告这是一个void的类型;

在这里插入图片描述

目的: 是为了保证 (单行函数)=> /*没有大括号*/ 的语法不被影响,虽然void 支持其他返回值,但是并不能使用;

//当箭头函数只有一句话可以省略 { 大括号 } 则会默认返回此行代码的返回值
// let upfun = (str: string) => { str.toUpperCase() };
let upfun = (str: string) => str.toUpperCase();
let up = upfun("abc");
console.log(up);

在这里插入图片描述

TypeScript 类:

关于类: 对于学习了解过后端语言的:Java、C#、py... 的朋友就会不会很陌生,可以说是 洒洒水啦😉😉~~

其实JavaScript: 在ES6 之后也引入了类的概念,这里有个人的总结blog👉: 本质来说类是用于创建对象而存在的概念性语法…

TypeScript 类的定义上提供了更清晰的语法、强类型检查、更方便的继承和接口实现机制以及访问修饰符,

使得代码更易于维护和理解,尤其在大型项目和团队协作中优势明显;

TypeScript Class类:

JavaScript 原型链ES6本身就提供了对类的定义使用: 这里TypeScript 对其进行类型检查:增强优化;

//TypeScript 数据类型 class
class Person {
    //属性声明: 并指定数据类型
    name: string
    age: number
    //构造器: 并指定数据类型
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    //方法:
    speak() { console.log(`我是:${this.name},今年${this.age}`) }
}

//////////////////////////////////////////////////////////////////////////////

//Student 继承 Person
class Student extends Person {
    // 子类特有属性
    grade: string
    // 构造器:在写的过程就可以感受到各种提示;
    constructor(name: string, age: number, grade: string) {
        super(name, age)
        this.grade = grade
    }
    // 子类自己的方法
    study() { console.log(`${this.name}正在努力学习中.....`) }

    // 重写从⽗类继承的⽅法 override 可以不加,为了标记验证方法是重写的)
    override speak() { console.log(`我是学生,我叫:${this.name},今年${this.age}岁,在读${this.grade}年级`) }
}

//使用:
let p1 = new Person("xxx", 18); p1.speak();
let s1 = new Student("sss", 18, "斗宗"); s1.speak();

在这里插入图片描述

属性 访问 修饰符:

  • public默认:访问修饰符 该成员可以在类的内部、外部以及子类中被访问;
  • protected访问修饰符: 许类成员在类的内部以及子类中被访问,但不能在类的外部直接访问;
  • private访问修饰符: 类成员只能在类的内部被访问。外部代码和子类都 无法直接访问private成员;
  • readonly只读属性: 只读属性是一种在初始化后就不能被重新赋值的属性,对于那些在对象生命周期内不应该被修改的值;
//TypeScript 数据类型 class
class Person2 {
    //属性声明: 并指定数据类型
    public name: string
    protected age: number
    private password: string
    readonly uid: number = 5400
    //构造器: 并指定数据类型
    constructor(name: string, age: number, password: string) {
        this.name = name
        this.age = age
        this.password = password
    }
    //方法:
    speak() { console.log(`我是:${this.name},今年${this.age}`) }
}

//////////////////////////////////////////////////////////////////////////////

//Student 继承 Person
class Student2 extends Person2 {
    // 子类特有属性
    grade: string
    // 构造器:在写的过程就可以感受到各种提示;
    constructor(name: string, age: number, password: string, grade: string) {
        super(name, age, password)
        this.grade = grade
    }
    // 重写从⽗类继承的⽅法 override 可以不加,为了标记验证方法是重写的)
    override speak() { console.log(`我是学生,我叫:${this.name},今年${this.age}岁,在读${this.grade}年级`) }
    // 子类自己的方法
    study() { console.log(`${this.name}正在努力学习中.....`) }
}

//使用:
let p2 = new Person2("xxx", 18, "123456"); p1.speak();
let s2 = new Student2("sss", 18, "654321", "斗宗"); s1.speak();

在这里插入图片描述

简写形式:

//简写形式:使用构造函数参数属性简写,通过在构造函数参数前添加访问修饰符
//就能同时完成属性声明和初始化。上面的代码可以简写成
class Person3 {
    readonly uid: number = 5400
    //构造器: 并指定数据类型
    constructor(public name: string, protected age: number, private password: string) {
        this.name = name, this.age = age, this.password = password
    }
}

abstract 抽象类:

抽象类是一种不能被实例化的类,它主要用于作为其他类的基类

抽象类通常包含抽象方法非抽象方法,抽象方法是没有具体实现的方法,它的实现必须在非抽象的子类中完成:

//创建一个形状的抽象类
abstract class Shape {
    // 非抽象属性、方法
    public color: string;
    constructor(color: string) { this.color = color; }
    printColor(): void { console.log('The color of this shape is ${this.color}'); }
    // 面积抽象方法,没有具体实现
    abstract getArea(): number;
		// 抽象方法没有方法体,它只是定义了方法的签名(包括方法名、参数列表和返回类型
}

抽象方法用于强制子类实现特定的行为。通过在抽象类中定义抽象方法,

所有继承该抽象类的子类都必须实现这些抽象方法,从而保证了子类具有统一的行为规范:

//圆形类 extends 形状
class Circle extends Shape {
    constructor(color: string, private radius: number) {
        super(color); this.radius = radius;
    }
    getArea(): number { return Math.PI * this.radius * this.radius; }
}
//矩形类 extends 形状
class Rectangle extends Shape {
    constructor(color: string, private width: number, private height: number) {
        super(color); this.width = width; this.height = height;
    }
    getArea(): number { return this.width * this.height; }
}

interface接口:

interface 是⼀种定义结构的⽅式,主要作⽤是为:类、对象、函数等规定⼀种 契约

确保代码的一致性和类型安全,但要注意: interface 只能定义格式,不能包含任何实现!!

/////////////////////////////////////////////定义类格式:
interface PersonInterface {
    name: string
    speak(n: string): void
}

class PersonImpl implements PersonInterface {
    // 自定义属性
    age: number
    // 实现接⼝中的属性\⽅法
    constructor(public name: string, age: number) {
        this.name = name
        this.age = age
    };
    speak(n: string): void {
        console.log('你好,我叫${this.name},我的年龄是');
    }
}
/////////////////////////////////////////////定义对象格式:
//接口可以约束对象字面量的结构,确保其符合特定的类型要求
//声明一个变量类型就是一个接口,有点类似于Type了
let perint: PersonInterface = {
    name: "perint",
    speak() { console.log(`你好,我叫${this.name}`) }
}

/////////////////////////////////////////////定义函数格式:
//可以定义接口来描述函数的类型,包括参数类型和返回值类型,进而约束函数的定义
//定义一个描述计算两个数求和操作的接口
interface CountInterface { (a: number, b: number): number; }
//然后创建符合这个接口的函数:
const count: CountInterface = (x, y) => { return x + y }
//调用
count(1, 2);

接口继承 自动合并:

TypeScript 中,接口可以通过继承来扩展其他接口的属性和方法,使用extends关键字来实现接口继承

//动物接口
interface Animal {
  name: string;
  age: number;
}
//狗狗接口
interface Dog extends Animal { breed: string; }
// Dog接口就继承了Animal接口的name和age属性,并且还新增了自己特有的breed属性

接口自动合并的规则和条件: 同名接口自动合并:当在不同的位置定义了同名接口时,TypeScript 会自动合并这些接口

//两个PersonS接口虽然是分开定义的,但 TypeScript 会自动将它们合并成一个包含firstName和lastName属性的接口
//合并顺序和冲突处理:接口合并是按照定义的先后顺序进行的。如果在合并过程中出现同名属性\类型冲突的情况,TypeScript 会报错
interface PersonS { firstName: string; }
interface PersonS { lastName: string; }
let someone: PersonS = {
    firstName: "John",
    lastName: "Doe"
};

与 抽象类 区别:

相同点: 都能定义⼀个类的格式定义类应遵循的契约

不相同:接⼝: 只能描述结构,不能有任何实现代码,⼀个类可以实现多个接⼝;

抽象类: 既可以包含抽象⽅法,也可以包含具体⽅法, ⼀个类只能继承⼀个抽象类;

与 type区别:

相同点: interfacetype 都可以⽤于定义对象结构,在定义对象结构时两者可以互换

不同点: type:可以定义类型别名、联合类型、交叉类型,但不⽀持继承和⾃动合并;

interface:更专注于定义对象和类的结构,⽀持继承、合并;

泛型 < T >:

泛型(Generics)是一种强大的工具,它允许你编写可以在多种类型上工作的代码,而不是针对特定的类型:

<T>是泛型的一种常见表示形式,T在这里是一个类型变量,可以代表任何类型;

////////////////////////////////////////泛型函数
//泛型使得代码能够适用于多种类型,而不需要为每种类型编写重复的代码
function logData<T>(data: T): T {
    console.log(data)
    return data
}
//支持任意数据类型
logData<number>(100)
logData<string>('hello')

//泛型可以有多个
function logData2<T, U>(datal: T, data2: U): T | U {
    console.log(datal, data2)
    return Date.now() % 2 ? datal : data2
}
logData2<number, string>(100, 'hello');
logData2<string, boolean>('ok', false);

泛型接口:可以定义包含泛型的接口,用于描述具有通用性的对象类型

////////////////////////////////////////泛型接口
interface PersonInterfaceT<T> {
    name: string,
    age: number
    extraInfo: T
}
let pT1: PersonInterfaceT<string>
let pT2: PersonInterfaceT<number>
pT1 = { name: '张三', age: 18, extraInfo: '一个好人' }
pT2 = { name: '李四', age: 18, extraInfo: 2500000000 }

声明文件:

类型声明⽂件是 TypeScript 中的⼀种特殊⽂件,通常以: .d.ts 作为扩展名 它的主要作用是为 JavaScript 代码提供类型信息

因为 TypeScript 是强类型语言,而 JavaScript 是弱类型语言,当在 TypeScript 项目中使用 JavaScript 库时:

  • 就需要声明文件来告诉 TypeScript 编译器这些 JavaScript 代码中的变量、函数、对象等类型;

假设你有一个简单的 JavaScript 函数function add(a, b) { return a + b; }

  • 如果要在 TypeScript 项目中使用这个函数,TypeScript 编译器并不知道ab的类型以及函数的返回值类型
  • 这时,就可以通过一个.d.ts文件来提供类型声明,如declare function add(a: number, b: number): number;
//在 TypeScript 项目中使用 JavaScript 库时;
//因为JS函数没有类型声明,所以会报错警告!!
import { add, mul } from "./demox.js";

const x = add(2, 3); // x 类型为 number
const y = mul(4, 5); // y 类型为number

在这里插入图片描述

注意: TS 的编译插件在编译TS时,是根据 CommonJS 的语法编译的,如果直接浏览器运行可能会出现:

Object.defineProperty(exports, __esModule, { value: true })

将tsconfig.json文件里面的 'module':'commonjs'注释掉

使用第三方库的.d.ts 文件:很多流行的 JavaScript 库都有对应的.d.ts文件;

例如,要在 TypeScript 项目中使用 jQuery,安装@types/jquery后,

TypeScript 编译器就可以获取 jQuery 库的类型声明;