TypeScript

TypeScript
LiSir认识TypeScript
了解TS
TypeScript是Microsoft公司注册商标。
TypeScript具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript代码。 TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。
TypeScript目前还在积极的开发完善之中,不断地会有新的特性加入进来。 因此本手册也会紧随官方的每个commit,不断地更新新的章节以及修改措词不妥之处。
安装TS
有两种主要的方式来获取TypeScript工具:
- 通过npm(Node.js包管理器)
- 安装Visual Studio的TypeScript插件
针对使用npm的用户:
1 | npm install -g typescript |
简单运行TS文件
新建一个index.ts文件
1 | function greeter(person) { |
在命令行中,使用tsc index.ts 来编译ts文件 —->生成同名的js文件
自动化编译TypeScript
因为ts不能够直接运行,需要我们手动转为js才能运行,为了方便开发者,我们需要将自动转为js这一操作开启。
命令行输入
tsc --init我们会得到一个名为tsconfig.json文件。该文件配置了ts转为js时的一些规则,比如转换成那个规范(es5,es6,es7)等。使用命令
tsc --watch,表示监视某个ts文件,当文件内容发生变化时,就转为js,如果不指定文件那该命令就是监视所有的ts文件,一旦发生变化就自动进行转换。
还有一点,当我开启全局监视时,确实完成了自动ts转js的功能,但是当我们ts文件中写的内容发生错误时,它依旧会直接进行转换,这个是我们不希望的。解决如下:
- 打开
tsconfig.json文件查找noEmitOnError,将其注释去掉即可。该配置项意味着出现错误就不要再提交进行转换了。
类型声明
基础语法:let 变量:变量类型
1 | let a: string; |
除了能定义变量,还能定义函数参数的类型和函数的返回值的类型,比如
1 | function add(x: number, y: number): number { |
这表示你要接受两个类型是数字型的参数。,并且返回值的类型也是数字型。
类型推断
TypeScript类型推断简介
类型推断是指TypeScript编译器自动地根据赋值来确定一个值的类型。这意味着在某些情况下,你不需要显式地指定类型,TypeScript可以根据上下文来推断出正确的类型。
1. 变量声明
当你声明一个变量并立即赋予一个值时,TypeScript会根据这个值的类型来推断该变量的类型。
1 | let myVariable = "Hello World"; // myVariable被推断为string类型 |
2. 函数参数
在函数参数中,如果没有显式地指定类型,TypeScript也会尝试根据函数体内的操作进行类型推断。
1 | function logMessage(message) { |
3. 函数返回值
当函数的返回值没有明确指定类型时,TypeScript会根据return语句的结果来推断返回值类型。
1 | function getFirstElement(arr) { |
4. 数组
对于数组,类型推断会根据初始化时的元素来确定数组元素的类型。
1 | let numbers = [1, 2, 3]; // numbers被推断为number[] |
5. 对象字面量
当定义一个对象字面量时,TypeScript会根据提供的键值对来推断对象的类型。
1 | let person = { name: "Alice", age: 25 }; // person被推断为{ name: string; age: number } |
注意事项
虽然类型推断可以使代码更加简洁,但在某些情况下,显式地指定类型可能会更加清晰并且有助于避免潜在的错误。因此,在使用类型推断时,应权衡其带来的便利性和可能的混淆性。
显式类型声明 vs 类型推断
- 显式类型声明:在声明时就清楚地定义了类型,适用于复杂的逻辑和需要明确类型的情况。
- 类型推断:减少了代码量,适用于简单和直白的情况。
类型总览
JavaScript数据类型
- string
- number
- boolean
- null
- undefined
- bigint
- symbol
- object
其中object包含:Array,Function,Date,Error等……
TypeScript数据类型
上述js中的所有类型
六个新类型
any
unknow
never
void
tuple
enum
新增了两个用于自定义类型的方式
type
interface
注意点
官方推荐在指定类型时,使用小写,比如
1 | let a: string; |
原始类型 VS 包装对象
自动装箱
JavaScript在必要时会将自动原始类型包装成对象,以便调用方法或访问属性。
类型断言
什么是类型断言?
类型断言(Type Assertion)是一种告诉编译器“你应该将此值视为这种类型”的方式。通常,当你从一个上下文获取一个值时,这个值可能是多种类型之一,这时你就需要使用类型断言来明确地指定这个值应该被视为哪种类型。
使用场景
类型断言常用于以下几种情况:
- 当你知道一个值的真实类型,但是TypeScript无法推断出来时。
- 在使用JavaScript的库时,这些库可能没有类型信息,或者类型信息不准确。
- 需要在联合类型或任何类型(
any)之间进行转换。
断言语法
TypeScript提供了两种形式的类型断言:
尖括号语法:
1
2let someValue: any = "this is a string";
let strLength: number = <string>someValue.length;as 关键字语法:
1
2let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
示例
假设你有一个函数,它可能返回多种类型的值,你想要使用其中一个类型的功能。
1 | function getFirstElement(array: any[]): any { |
注意事项
- 类型兼容性:断言只能在兼容的类型之间进行。例如,你不能将一个
string断言为boolean。 - 滥用
any类型:尽量避免使用any类型,因为这会绕过TypeScript的所有类型检查。如果必须使用any,在可能的情况下尽快将其断言回一个具体的类型。 - 类型安全:类型断言不会改变运行时的实际值,只影响TypeScript编译时的类型检查。如果断言错误,可能会导致运行时错误。
常用类型
any
any:含义是任意类型,一旦将变量类型限制为any,那就意味着放弃了对该变量的类型检查
- any类型的变量可以赋值给任意类型的变量。!!!
unknown
unknown:含义是未知类型。
unknow可以理解为一个类型安全的
any,适用于:不确定数据的具体类型。当我们给某个变量类型定义为known时,会出现一些问题(比如:”str的类型为未知”),这时候解决问题有三种方法:1.使用条件判断。2.使用类型断言,明确数据类型。
never
never:含义是任何值都不是,简单说就是不能有值,undefined、null、' '、0都不行!
- 实际开发中,几乎不使用never去限制变量类型
- never一般是TS主动推断出来的。
- never可以用来限制函数的返回值
void
void:通常用于函数返回值的声明。含义:函数返回值为空,调用者也不应该依赖于函数的返回值去做任何操作。
- undefined是void可接受的一种空。
- 若函数的返回值是
void,则:1.从语法上讲:函数可以返回undefined,至于显式返回还是隐式返回,无所谓。2.函数调用者不应该关心函数的返回值,也不应该依赖于函数的返回值去做任何操作。即使明白该函数返回了undefined
object和Object
object:(该类型开发不常用,因为限制的范围太大了)该类型的含义是:能够存储非原始类型数据。
Object:该类型的含义是:除了null类型和undefined类型,其他类型的数据都可以存储。
拓展1:声明对象类型
1 | //声明对象类型的数据 - 用逗号 ?表示age是可选属性 |
拓展2:声明函数类型
1 | //let 函数名 = (函数参数) => 返回值类型 |
通过上面那样定义,表示:该变量count,接受一个返回值是number类型,且携带了a,b两个数字类型的参数的函数。
拓展3:声明数组类型
1 | let arr = string[] // - 表示声明了一个存储字符串类型的数组。 |
基础语法:数据类型[] / Array<数据类型>
tuple
元组(tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型都是已知的且可以不同。元组用于精确描述一组值的类型,?表示可选元素。
基本语法:
1 | let arr = [string,number]; |
这表示该元祖存储的数据类型必须是string和number且string在前,number在后。
enum
枚举类型。枚举可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护。枚举是一个对象。
常量:无法更改其值。
基本语法:enum + 枚举名(一般开头大写) { }
数字枚举
数字枚举存在反向映射。也就是0 - up,1-down,2-left,3-right
1 | enum Direction { |
查看控制台:
字符串枚举
字符串枚举不存在反向映射
1 | enum StrEnum { |
查看控制台:发现并不存在有值 - 键的关系。
常量枚举
常量枚举是一种特殊的枚举类型,它使用const关键字定义,在编译时会被内联,避免生成一些额外代码。
如果我像下面这样写
1 | //数字枚举 |
那么生成的js文件是下面这样的:多且杂
1 | ; |
如果我像下面这样写 - 这种写法就被称为常量枚举。
1 | //数字枚举 |
生成的js文件就是这样的
1 | console.log(0 /* Direction.up */); //其实是个对象 |
type
在TypeScript中,type关键字用于创建类型别名(Type Aliases),这可以帮助简化复杂的类型名称,使得代码更加清晰易读。下面是一些关于TypeScript中的type类型的简要学习笔记:
1. 基础类型定义
TypeScript 中的基础类型包括 string, number, boolean, null, undefined, void, any, unknown 等。你可以为这些基础类型创建一个别名:
1 | type Name = string; |
2. 联合类型
联合类型允许将几种类型合并为一种类型:
1 | type ID = number | string; |
3. 交叉类型
交叉类型允许组合多种类型来形成一个新的类型:
1 | type Person = { |
4. 类型别名与接口
类型别名和接口都可以用来描述对象的形状,但是它们有一些不同之处。类型别名可以合并类型,而接口则可以扩展:
1 | type Animal = { name: string; }; |
一个特殊况情况
case1:声明函数的同时限制返回值类型为void
看下面这段代码
1 | //定义函数的同时也限制返回值类型 - void 并声明函数。 |
case2:先定义函数的类型和声明返回值为void,然后再利用变量去声明函数。
1 | //如果我们先定义函数和声明返回值类型,在进行函数的声明,就比如 |
上述看着好像没多大问题,但是实际上,问题就在于如果我们拿到了这个返回值,并使用返回值进行一些逻辑编写,还是会报错。比如
1 | if(f1()){ |
这点要注意了。
类相关知识
最基本类的定义
1 | class Person { |
类的继承
使用extends来继承父类。
注意点:
- 如果想要在子类中添加新的属性,需要利用constructor构造器来添加,并且构造器中必须包含父类中已有的属性,随后在添加子类独有的属性。
- 在子类中能够重写父类的方法。注意要同名,如不同名则会被TS认为是子类独有方法。并利用override修饰重写的方法。
1 | //该类继承上面的Person类 |
上述代码中的super方法实际上是在调用父类中的constructor方法
属性修饰符
| 关键字 | 描述 | 示例 |
|---|---|---|
public |
默认的可见性级别,表示该成员可以在任何地方被访问。 | |
private |
表示只有在声明它的类内部可以访问该成员。 | |
protected |
表示该成员可以在声明它的类和其子类中访问。 | |
readonly |
表示该成员一旦初始化后就不能被重新赋值。 | |
static |
表示该成员属于类本身,而不是类的实例。 | |
引入修饰符后我们可以引出新概念:属性的简写形式
属性的简写形式
简写前:
1 | class Person { |
简写后:
1 | class Person { |
可以发现,通过修饰符修饰属性,就省略了属性的声明和赋值操作,直接大大减少代码量。以后要多用。
抽象类
抽象类是通过关键字abstract来声明的,抽象类是一类无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法。
抽象类概念
简单理解:抽象类不能被实例化,可以继承,抽象类里面可以有普通方法,也可以有抽象方法
抽象方法要用abstract来修饰,并且抽象函数不需要函数体,比如,抽象方法更像是一种形式,我并没有具体要怎么做,我只是声明了一个方法。
1 | abstract class Package { |
抽象类的使用
想要使用抽象类,就必须通过继承来实现。继承的关键字是extends,来看下面这个例子。
1 | class StandardPackahge extends Package { |
何时使用抽象类?
- 定义通用接口:为一组相关的类定义通用的行为(方法或属性)时。
- 提供基础实现:在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现了。
- 确保关键实现:强制派生类实现一些关键行为。
- 共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。
interface接口
interface是一种定义结构的方式,主要作用是为:类,对象,函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意interface只能定义格式,不能包含任何实现!
就拿在AI图生图项目中,定义的一组表单接口如下
1 | interface formType { |
接口定义类
利用implements关键字来实现这个类
1 | interface PersonInterface { |
上面定义了类接口,下面就实现这个类接口 - 通过implements关键字
1 | class Person implements PersonInterface { |
接口定义对象
在利用接口定义一个对象类型是,接口更像是一种类型。使用方式也和类型一样
1 | interface PeosonInterface{ |
上面定义了一个人的接口,使用如下
1 | const user:PeosonInterface = { |
接口定义函数
当你需要定义一个函数的类型时,可以使用接口来描述这个函数应该接受什么样的参数以及返回什么样的值。
1 | interface SearchFunc { |
接口可以继承接口
比如说
1 | interface Student { |
我还有一个接口
1 | //让班级接口继承学生接口 |
可以看到,也就是s1这个对象中,必须有Student接口的属性和Grade接口的属性,缺一不可。
接口自动合并
接口可以进行重复定义:
1 | interface PersonInterface { |
最终使用该接口时,就要求包含这三个属性,也就是说底层认为,你定义了一个接口
1 | interface PersonInterface { |
何时使用接口?
- 定义对象的格式:描述数据、API响应格式、配置对象等等…..开发常用
- 类的契约:规定一个类中需要哪些属性和方法,以及他们的类型
- 自动合并:拓展第三方库的类型。
区分一些相似概念
interface和type的区别
- 相同点:interface和type都可以用于定义对象结构,在许多场景中可以互换
- 不同:
- interface:更专注于对象和类的结构,能够继承和自动合并
- type:可以定义类型别名、联合类型、交叉类型,不支持继承和自动合并
interface和抽象类的区别
- 都用于定义一个类的格式(应该遵守的规则)
- 不同点:
- 接口:只能描述类的接口,不能有任何实现代码,一个类可以实现(implements)多个接口
- 抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承(extends)一个抽象类
泛型
泛型允许我们在定义函数、类、接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定为具体的类型,泛型能让同一段代码适用多种类型,同时任然保持类型的安全性。我们用<>来表示泛型。
泛型函数
举例:比如在一个函数中:
1 | //泛型举例 |
泛型函数可以有多个
1 | function logData<T,U>(params1:T,params2:U){ |
泛型接口
泛型接口也很好理解:关键在于使用时在具体指定类型
1 | //泛型接口 |
泛型类
泛型类:任旧是在具体使用时传入具体类型
1 | type jobInfo = { |
类型声明文件
在TypeScript中,类型声明文件是用来定义类型信息的特殊文件。这些文件通常用于描述非TypeScript代码(如JavaScript库)的类型结构,或者为项目中的其他部分提供类型信息而不包含任何实现逻辑。类型声明文件通常以.d.ts扩展名结尾,表示“declaration”文件。
类型声明文件的作用
- 提高开发体验:通过提供类型信息,可以增强IDE的功能,如智能提示、自动补全等。
- 确保类型安全:当项目使用了类型声明文件后,TypeScript编译器可以在编译阶段检查类型兼容性,提前发现潜在错误。
- 简化API使用:良好的类型定义可以帮助开发者更快地理解第三方库或模块的API,从而更方便地使用它们。
基本语法
类型声明文件可以包含以下元素:
- 接口(Interfaces):定义对象的形状。
- 类型别名(Type Aliases):定义复杂类型的别名。
- 类类型(Class Types):描述类的静态成员和实例成员。
- 函数类型(Function Types):定义函数参数和返回值的类型。
- 变量声明(Variable Declarations):声明全局变量或模块作用域内的变量。
- 模块声明(Module Declarations):描述全局模块或命名空间。
- 命名空间(Namespaces):组织相关类型。
- 枚举(Enums):定义一组命名常量。
- 导入/导出(Imports/Exports):从其他模块导入类型或向其他模块导出类型。
TypeScript装饰器
- 装饰器的本质是一种特殊的函数,它可以对:类、属性、方法、参数进行拓展,同时让代码更简洁。
- 装饰器自2015年在
ES6中被提出到现在,已经10年。 - 截止目前,装饰器依然是实验性特性,需开发者手动调整配置,来开启装饰器。
- 装饰器有5种:
- 类装饰器
- 属性装饰器
- 方法装饰器
- 访问器装饰器
- 参数装饰器
类装饰器
类装饰器是一个应用在类声明上的函数,可以为类添加额外功能,或添加额外逻辑。
基本语法
1 | //类装饰器 - 本质是一个函数 |
类装饰器应用举例
1 | //类装饰器 - 本质是一个函数 |
关于装饰器的返回值
- 如果装饰器有返回值,且返回值是一个类,那么返回值的类会代替被装饰器装饰的类。
- 如果装饰器没有返回值或返回
undefined,那装饰器的类不会被替换。
未启用装饰器
1 |
|
控制台:
启动装饰器
1 |
|
控制台:
关于构造类型
声明构造类型
- new: 表示该类型可以使用new操作符调用。
- …args:表示该类型可以接受任何数量的参数
- any[]:表示该类型可以接受任意类型的参数。
- {} :表示返回的是一对象类型(非null,非undefined)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//声明构造类型
/*
new: 表示该类型可以使用new操作符调用。
...args:表示该类型可以接受任何数量的参数。
any[]:表示该类型可以接受任意类型的参数。
{} :表示返回的是一对象类型(非null,非undefined)
*/
type Constructor = new (...args:any[]) => {}
//使用构造类型
function test(fn:Constructor){
console.log(fn);
}
class PersonTest{
//必须要有静态属性wife
static wife:string
}
//可以传递因为类符合上述类型条件
test(PersonTest)声明构造类型的同时还能规定静态属性
1
2
3
4
5
6
7
8
9
10
11
12
13type Constructor = {
new (...args:any[]):{},
wife:string
}
//使用构造类型
function test(fn:Constructor){
console.log(fn);
}
class PersonTest{
//必须要有静态属性wife
static wife:string
}
这段代码的意思是
定义了一个类型别名 Constructor,它实际上是一个接口,描述了一个构造函数的要求。这个构造函数应该能够接受任意数量的参数(由 ...args:any[] 表示的 rest 参数),并且能够返回一个对象 {}。此外,还要求这个构造函数本身具有一个名为 wife 的字符串属性。
替换被装饰的类
可以看到,不仅获得原来类的属性,同时也新添加了一些新的属性和方法。
1 |
|
装饰器工厂
所谓的装饰器工厂:声明一个函数,该函数的返回值是一个装饰器,即装饰器工厂。该写法可以给装饰传递参数。
1 | //装饰器工厂:声明一个函数,该函数的返回值是一个装饰器,即装饰器工厂。 |
装饰器组合
装饰器组合使用,执行顺序是:先 [由上到下] 执行装饰器工厂,获取到装饰器后,再[由下到上]执行所有装饰器
1 | //装饰器 |
测试结果如下:
属性装饰器
属性装饰器也是一个函数,他会收到两个参数,第一个参数会因为修饰的属性而改变,第二个参数是修饰的属性
1 | function logProperty(target: object, propertyName: string) { |
说明:
- 如果属性装饰器修饰的是实例属性,那么target这个参数指的是该类的原型对象。
- 如果属性装饰器修饰的是静态属性,那么target这个参数指的是这个类本身
图示:
xxxxxxxxxx /* 约束屏幕尺寸 */@media screen and (max-width: 1024px) { html { font-size: 42px !important; }}@media screen and (min-width: 1920px) { html { font-size: 80px !important; }}css
关于属性遮蔽问题
简单说明一下这个问题:问题就是:代码执行顺序
- 如果我们先进行实例化
const p5 = new User('张三',20);那么在类的实例属性上就会存在age属性,那么这时去修改age属性是不会触发原型对象上的get和set函数的。
1 | class User { |
- 如果我们先在类的原型对象上添加一个实例属性
age的话,然后再实例化类,这时就会去调用构造器,执行this.age = age,但是实例上还没有age这个属性,接下来就沿着原型链去原型对象上查找,此时发现原型对象中存在age属性,且值为30,于是就把原型对象上的age属性交给了实例对象的age属性。那么实际上就相当于,当我们修改实例对象的age属性时,其实是修改的原型对象上的age属性。
1 | class User { |
属性装饰器的应用举例
场景:封装一个监视性的装饰器State来监视某个属性,当其发生变化时,打印变化的值。
1 |
|
方法装饰器
方法装饰器依旧是一个函数,具体如下:
当我们装饰一个类的实例方法时:
- target:是被装饰方法所在类的原型对象
- propertyKey:被装饰的方法的方法名
- descriptor:被装饰方法的描述
图示:
1 | function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){ |
当我们装饰一个类的静态方法时:
- target:是被装饰方法所在的类。
- propertyKey:被装饰的方法的方法名
- descriptor:被装饰方法的描述
图示:
1 | function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){ |
方法装饰器的应用
能够实现:在方法调用前执行一些逻辑,在方法调用后执行一些逻辑。
1 | function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){ |
控制台输出
访问器装饰器
访问器装饰器是装饰类中的get和set方法的一种装饰器。
参数说明:
target:
- 对于实例中的访问器来说,target指的是该访问器所属的原型对象。
- 对于静态访问器来说,target指的是该访问器所属的类
propertyKey:属性访问器的名称
descriptor:属性访问器的描述。
基本语法:
1 | function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){ |
访问器装饰器举例
给set加一个限制,表示我们不能随意设置任意值。
1 | //为了能传递参数,这里我们使用装饰器工厂 |
参数装饰器
在 TypeScript 中,参数装饰器是一种特殊类型的装饰器,它们应用于类的方法或构造函数的参数声明。当在方法或构造函数的参数前使用装饰器时,这些装饰器即为参数装饰器。参数装饰器接收三个参数:
- 对于类的方法来说,第一个参数是
target(原型对象)。 - 第二个参数是方法的
propertyKey(即方法名)。 - 第三个参数是索引签名,表示被装饰的参数的位置(从0开始计数)。
下面是一个简单的例子,展示了如何使用参数装饰器:
1 | // 参数装饰器 |
在这个例子中,@required 被用作 greet 方法中 name 参数的装饰器。每次创建 Greeter 实例并调用其 greet 方法时,required 装饰器就会被调用,并打印出装饰器接收到的信息。















