编辑
2025-08-10
前端
00

目录

本文参考内容
1 TS 介绍
2 类型声明和类型推断
3 各种类型
1 any、unknown、never
2 基本类型和包装类型
3 Object 和 object
4 undefined 和 null
5 值类型
6 联合类型 (并集)
7 交叉类型 (交集)
8 数组
8.1 声明方式
8.2 只读数组
9 元组
10 对象
11 函数
12 enum 类型
12.1 enum 的值
12.2 enum 的合并
12.3 反向映射
12.4 eunm 的本质
4 type 和 interface
1 interface 语法
2 interface 的继承
2.1 interface 继承 interface
2.2 interface 继承 type
2.3 interface 继承 class
3 interface 的合并
4 interface 和 type 的区别
5 ts 的 class
6 ts 的泛型
1 泛型的写法
1.1 函数的泛型写法
1.2 接口的泛型写法
1.3 类的泛型写法
1.4 类型别名的泛型写法
2 泛型的默认值
3 泛型的约束条件
4 注意点
7 模块
1 import export type 语句
2 commonjs 模块
2.1 import = 语句
2.2 export = 语句
2.3 模块定位
2.3.1 相对模块、非相对模块
2.3.2 classic 方法
2.3.3 node 方法
2.3.4 路径映射
8 装饰器
1 装饰器版本
2 装饰器的结构
3 装饰器的分类
3.1 类装饰器
3.2 方法装饰器
3.3 属性装饰器
3.4 gettter、setter 装饰器
3.5 accessor 装饰器
4 装饰器的执行顺序
9 declare 关键字
10 d.ts 类型声明文件
1 类型声明文件的来源
1.1 自动生成
1.2 内置声明文件
11 类型运算符
1 keyof 运算符
2 typeof 运算符
3 in 运算符
4 方括号运算符
5 extend...?:条件运算符
6 infer 关键字
7 is 运算符
8 satisfies 运算符
12 类型守卫(类型保护)、类型谓词、类型断言、类型兼容
1 类型守卫(类型保护)
2 类型谓词
3 类型断言
4 类型兼容
5 协变、逆变、双向协变、不变
5.1 子类型和可赋值性
5.2 协变
5.3 逆变
5.4 双向协变
5.5 不变
13 索引签名和类型映射
1 索引签名
2 类型映射
2.1 映射修饰符
2.2 键名重映射
2.2.1 属性过滤
14 类型工具
1 Promise
1.1 Awaited
2 联合类型
2.1 Exclude(对应 Extract)
2.2 NonNullable
3 对象
3.1 InstanceType
3.2 Omit
3.3 Partial(对应 Required)
3.4 Pick
3.5 Readonly
3.6 Record
4 函数
4.1 Parameters
4.2 RetureType
15 注释指令
16 tsconfig.json
17 TypeScript 编译原理

本文为本人学习 TS 时阅读资料整理的笔记,若有不完善或者不准确的部分,还请谅解。

本文参考内容

入门

进阶

1 TS 介绍

TS 是一种静态类型语言,有这几方面的优点

  1. 有利于代码的静态分析,不必运行代码,就可以分析代码的类型问题
  2. 有利于发现错误
  3. 更好的 IDE 支持,语法提示和自动补全
  4. ... 其他一些不重要

也有一些缺点

  1. 失去了动态类型的灵活性
  2. 增加了编程工作量
  3. 更高的学习成本

2 类型声明和类型推断

  1. 类型声明

基本语法是,类型后置,这种类型声明方式在 rust、kotlin 和 python3 等语言中都有使用

ts
let foo:string
  1. 类型推断

如果没有声明类型,ts 会自动声明类型

ts
let foo = 123; // 这个时候foo已经被推断为了number类型 foo = 'hello'; // 报错

ts 也可以推断函数的返回值

ts
function toString(num:number) { return String(num); }

ts 在这里的设计思想是,类型声明是可选的,即使不加类型声明,也会自动推断变量或者函数的类型

3 各种类型

1 any、unknown、never

any 类型

  • any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值
  • any 的使用场景
    1. 特殊场景需要关闭变量的类型检查,使用 any
    2. 迁移老 js 项目,先设置为 any,再一步一步迁移到 ts
  • 从集合论的角度讲,any 类型可以看作其他类型的全集,ts 将其叫做"顶层类型"
  • 类型推断
    • 如果 ts 的自动类型推断无法推断出变量的类型,就会将其推断为 any 类型
    • 如果想要阻止推断为 any 类型,可以开启 noImplicitAny(不能存在隐式 any

unknown 类型

  • unknownany 的相似之处,在于所有类型的值都可以分配给 unknown 类型
  • unknown 类型跟 any 类型的不同之处在于,它不能直接使用,主要有以下几个限制
    • unknown 类型的变量,不能直接赋值给其他类型的变量(除了 any 类型和 unknown 类型)
    • 不能直接调用 unknown 类型变量的方法和属性
    • unknown 类型变量能够进行的运算是有限的,只能进行比较运算(运算符 =====!=!==||&&?)、取反运算(运算符 !)、typeof 运算符和 instanceof 运算符这几种,其他运算都会报错
  • unknown 类型的使用需要经过"类型缩小",缩小 unknown 变量的类型范围,确保不会出错
ts
let a: unknown = 1; // 缩小为 number 类型 if (typeof a === "number") { a.toFixed(1); } // 缩小为 string 类型 if (typeof a === "string") { a.trim(); }
  • 在集合论上,unknown 也可以视为所有其他类型(除了 any)的全集,所以它和 any 一样,也属于 TypeScript 的顶层类型

never 类型

  • 为了保持与集合论的对应关系,以及类型运算的完整性,TypeScript 还引入了“空类型”的概念,即该类型为空,不包含任何值
  • 使用场景
    • 在一些类型运算之中,保证类型运算的完整性(如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于 never 类型)
    • 不可能返回值的函数,返回值的类型就可以写成 never
  • never 可以赋值给任意类型,因为在集合论中,never 为空集,空集是所有集合的子集,所以可以赋值
  • never 是一个底层类型

2 基本类型和包装类型

ts 中的基本类型和 js 的基本类型是一样的

  • boolean 包含 truefalse
  • string 字符串
  • number 数字类型,包括整数和浮点数
  • bigint 大整数
  • symbol
  • object 包括对象、数组、函数
  • undefinednull,各自代表一种类型
    • 在关闭 noImplicitAnystrictNullChecks 的时候,如果变量设置为 undefined 或者 null,变量类型为 any,因此需要开启

包装类型

js 的8种类型,undefinednull 是两个特殊值,object 属于复合类型,剩下的5种属于原始类型,代表最基本的,不可再分的值

这5种基本类型,又有对应的包装类型,而 Symbolbigint 不能作为构造函数使用,也就没有对应的包装类型,所以 ts 中基本类型的包装类型包括以下

  • Boolean
  • String
  • Number

包装类型和字面量类型 TypeScript 对五种原始类型分别提供了大写和小写两种类型。

  • Boolean 和 boolean
  • String 和 string
  • Number 和 number
  • BigInt 和 bigint
  • Symbol 和 symbol 其中,大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象

注意:symbol 和 bigint 的大小写没有区别,原因上面解释了 (不能作为构造函数使用)

3 Objectobject

大写 Object

  • 大写的 Object 类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是 Object 类型
  • 除了 undefinednull 这两个值不能转为对象,其他任何值都可以赋值给 Object 类型
  • 空对象 {}Object 类型的简写形式

小写 object

  • 小写的 object 类型代表 JavaScript 里面的狭义对象,即可以用字面量表示的对象,只包含对象、数组和函数,不包括原始类型的值

4 undefinednull

undefinednull 既是值,又是类型。

  • 作为值,它们有一个特殊的地方:任何其他类型的变量都可以赋值为 undefinednull。但是可能导致无法取到变量上的一些属性和方法。
  • 因此提供了 strictNullChecks 选项,让 undefinednull 不能赋值给其他类型变量,防止报错

5 值类型

TypeScript 规定,单个值也是一种类型,称为“值类型”。

TypeScript 推断类型时,遇到 const 命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型。

ts
// x 的类型是 "https" const x = 'https';

只包含单个值的值类型,用处不大。实际开发中,往往将多个值结合,作为联合类型使用。

6 联合类型 (并集)

联合类型:一个值可以是多种类型中的任意一种(“或”的关系)

联合类型(union types)指的是多个类型组成的一个新类型,使用符号 | 表示。

联合类型 A|B 表示,任何一个类型只要属于 AB,就属于联合类型 A|B

类型缩小”是 TypeScript 处理联合类型的标准方法,凡是遇到可能为多种类型的场合,都需要先缩小类型,再进行处理。实际上,联合类型本身可以看成是一种“类型放大”(type widening),处理时就需要“类型缩小”(type narrowing)。

7 交叉类型 (交集)

交叉类型:一个值必须同时满足所有类型(“且”的关系),拥有所有成员

交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号 & 表示。

交叉类型 A&B 表示,任何一个类型必须同时属于 AB,才属于交叉类型 A&B,即交叉类型同时满足 AB 的特征。

交叉类型的主要用途是表示对象的合成。

8 数组

8.1 声明方式

  1. 第一种
ts
let arr: number[] = [1,2,3]
  1. 第二种
ts
let arr: Array<number> = [1,2,3]

8.2 只读数组

声明方式:

  1. 使用 readonly 关键字
ts
const arr: readonly number[] = [0, 1]
  1. 使用只读泛型
ts
const arr: Readonly<number[]> = [0, 1] const arr: ReadonlyArray<number[]> = [0, 1]
  1. 使用 const 断言
ts
const arr = [0, 1] as const;

9 元组

它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。

比如:

ts
const s:[string, string, boolean] = ['a', 'b', true];

元组有以下语法:

  1. 元组成员的类型可以添加问号后缀(?),表示该成员是可选的。

注意,问号只能用于元组的尾部成员,也就是说,所有可选成员必须在必选成员之后。

ts
let a:[number, number?] = [1];
  1. 使用 ... 拓展运算符,可以创建表示无限成员数量的元组

扩展运算符(...)用在元组的任意位置都可以,它的后面只能是一个数组或元组

ts
type t1 = [string, number, ...boolean[]]; type t2 = [string, ...boolean[], number]; type t3 = [...boolean[], string, number];
  1. 元组的成员可以添加成员名,这个成员名是说明性的,可以任意取名,没有实际作用。
ts
type Color = [ red: number, green: number, blue: number ]; const c:Color = [255, 255, 255];
  1. 元组可以通过方括号,读取成员类型。由于元组的成员都是数值索引,即索引类型都是 number,所以可以像下面这样读取。
ts
type Tuple = [string, number]; type Age = Tuple[1]; // number type Tuple = [string, number, Date]; type TupleEl = Tuple[number]; // string|number|Date
  1. 只读元组,语法和只读数组一样,这里不重复了

  2. 成员数量的推断

正常使用元组可以推断出成员数量,但是如果使用了可选成员或者拓展运算符,就获取不到成员数量,会爆 ts 错误

10 对象

ts 中对象包括以下常用语法

  1. 可选属性,如果某个属性是可选的(即可以忽略),需要在属性名后面加一个问号。
ts
type User = { firstName: string; lastName?: string; }; // 等同于 type User = { firstName: string; lastName: string|undefined; };

使用方式

ts
// 写法一 let firstName = (user.firstName === undefined) ? 'Foo' : user.firstName; let lastName = (user.lastName === undefined) ? 'Bar' : user.lastName; // 写法二 let firstName = user.firstName ?? 'Foo'; let lastName = user.lastName ?? 'Bar';
  1. 只读属性,属性名前面加上 readonly 关键字,表示这个属性是只读属性,不能修改。

  2. 属性名索引,除了 string,还可以是 numbersymbol

ts
type MyObj = { [property: string]: string };
  1. 解构赋值声明类型,只能给整体声明类型,不能给单个变量指定类型

  2. “结构类型“原则,只要对象 B 满足对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则。比较抽象,反正大概就是下面这样的代码

ts
const B = { x: 1, y: 1 }; const A:{ x: number } = { x: 1, y: 1 // 字面量方式声明报错 } const A: {x: number } = B // 变量方式赋值不报错
  1. 严格字面量检查,如果对象使用字面量表示,会触发 TypeScript 的严格字面量检查(strict object literal checking)。如果字面量的结构跟类型定义的不一样(比如多出了未定义的属性),就会报错。刚好和"解构类型"原则相反
ts
const point:{ x:number; y:number; } = { x: 1, y: 1, z: 1 // 报错 };
  1. 最小可选属性原则,如果某个类型的所有属性都是可选的,那么该类型的对象必须至少存在一个可选属性,不能所有可选属性都不存在。这就叫做“最小可选属性规则”。

11 函数

ts 函数类型的两种声明方式

  1. 普通函数的类型声明,类型直接写在函数上
ts
function add(x: number, y: number): number { return x + y }
  1. 箭头函数的声明方式,也有三种方式
ts
// 方式1 const add = (x: number, y: number): number => { return x + y } // 方式2 const add: (x: number, y: number) => number = (x, y) => { return x + y } // 方式3 type Add = (x: number, y: number) => number const add: Add = (x, y) => { return x + y }

函数也可以使用 typeof 关键字获取类型

ts
function add(x: number, y: number): number { return x + y } const add2: typeof add = (x, y) => { return x + y }

可选参数

ts
function f(x?:number) { // ... } f() // 正确 function f(x:number|undefined) { return x; } f() // 错误 如果是这样定义,需要传入undefined

参数默认值,设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值。

ts
function createPoint( x:number = 0, y:number = 0 ):[number, number] { return [x, y]; } createPoint() // [0, 0]

参数解构,写法如下

ts
function f( [x, y]: [number, number] ) { // ... } function sum( { a, b, c }: { a: number; b: number; c: number } ) { console.log(a + b + c); } // 默认值写法 function add({ x = 10, y = 10 }: { x?: number, y?: number }): number { return x + y } console.log(add({}))

rest 参数,rest 参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。

ts
// rest 参数为数组 function joinNumbers(...nums:number[]) { // ... } // rest 参数为元组 function f(...args:[boolean, number]) { // ... } // 最后一个参数可选 function f( ...args: [boolean, string?] ) {} // 利用灵活的元组的rest参数 function f(...args:[boolean, ...string[]]) { // ... } f(false, '1', '2', '4')

readonly 只读参数

ts
function arraySum( arr:readonly number[] ) { // ... arr[0] = 0; // 报错 }

void 类型void 类型表示函数没有返回值,并不是说函数不返回值,而是说返回值不重要

ts
type voidFunc = () => void; const f:voidFunc = () => { return 123; // 不报错 }; function f():void { return true; // 报错 } const f3 = function ():void { return true; // 报错 };

never 类型,表示肯定不会出现的值,比如函数的返回值,包括

  • 抛出错误的函数
  • 无限执行的函数 never 类型不同于 void 类型。前者表示函数没有执行结束,不可能有返回值;后者表示函数正常执行结束,但是不返回值,或者说返回 undefined

高阶函数,一个函数的返回值还是一个函数,那么前一个函数就称为高阶函数(higher-order function)


函数重载,有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为

ts
function reverse(str:string):string; function reverse(arr:any[]):any[]; function reverse( stringOrArray:string|any[] ):string|any[] { if (typeof stringOrArray === 'string') return stringOrArray.split('').reverse().join(''); else return stringOrArray.slice().reverse(); }

构造函数的类型

  • 构造函数的类型写法,就是在参数列表前面加上 new 命令
  • 构造函数还有另一种类型写法,就是采用对象形式
  • 某些函数既是构造函数,又可以当作普通函数使用,比如 Date(),可以两种函数的类型定义可以写在对象里
ts
class Foo { b: string; } // 构造函数的类型 type FooContructor = new () => Foo; // 构造函数类型的对象写法 type FooContructor = { // 构造函数的定义 new (): Foo; // 普通函数的定义 (): string; }; function create(c: FooContructor): Foo { return new c(); } const foo = create(Foo);

12 enum 类型

Enum 结构,用来将相关常量放在一个容器里面,方便使用,默认如果不赋初始值,枚举值将依次增加,比如

ts
enum Color { Red, // 0 Green, // 1 Blue // 2 }

Enum 结构本身也是一种类型。比如,上例的变量 c 等于 1,它的类型可以是 Color,也可以是 number

ts
let c:Color = Color.Green; // 正确 let c:number = Color.Green; // 正确

Enum 编译之后会变成 JS 代码,这是一个很特别的点,因为 TS 的定位是 JS 语言的类型增强,所以谨慎使用 Enum 结构,很大程度上,Enum 可以被对象的 as const 断言替代,它也可以作为类型

ts
enum Foo { A, B, C, } const Bar = { A: 0, B: 1, C: 2, } as const; // 对象的as const 断言写法也可以作为类型,即使用 typeof const Bar = { A: 0, B: 1, C: 2, } as const; const a: typeof Bar = { A: 0, }

Enum 之前也可以加上

注意:TypeScript 5.0 之前,Enum 有一个 Bug,就是 Enum 类型的变量可以赋值为任何数值。

ts
enum Bool { No, Yes } function foo(noYes:Bool) { // ... } foo(33); // TypeScript 5.0 之前不报错

12.1 enum 的值

enum 支持字符串和数字两种,数字可以是除了 bigint 之外的所有数字

对于数字类型的枚举来说,enum 支持反向映射 (即反向查找),而字符串类型不行

ts
enum A { white=4 } console.log(A[4]) // "white"

12.2 enum 的合并

多个同名的 Enum 结构会自动合并 作用:

  • 补充外部定义的 Enum 结构 注意事项:
  • Enum 结构合并时,只允许其中一个的首成员省略初始值,否则报错。
  • 同名 Enum 合并时,不能有同名成员,否则报错。
  • 同名 Enum 合并的另一个限制是,所有定义必须同为 const 枚举或者非 const 枚举,不允许混合使用。

12.3 反向映射

即使用数字类型的 enum,不仅可以通过键找到值,还可以通过值找到键

ts
enum A { Red, Blue, Green, } console.log(A.Red) // 0 console.log(A[0]); // Red

12.4 eunm 的本质

enum 本质是将枚举编译成了一个对象

比如如下代码

ts
enum A { Red, Blue=2, Green } console.log(A) // 打印结果为: /** { "0": "Red", "2": "Blue", "3": "Green", "Red": 0, "Blue": 2, "Green": 3 } */

enumconst enum 的编译区别是

ts
// ts代码 const enum A { Red, Blue, Green, } console.log(A.Red) console.log(A.Blue) console.log(A.Green) // enum编译结果 var A; (function (A) { A[A["Red"] = 0] = "Red"; A[A["Blue"] = 1] = "Blue"; A[A["Green"] = 2] = "Green"; })(A || (A = {})); console.log(A.Red); console.log(A.Blue); console.log(A.Green); // const enum 编译结果 console.log(0 /* A.Red */); console.log(1 /* A.Blue */); console.log(2 /* A.Green */);

其中 enum 的编译结果咋一眼看过去看不懂,实际上可以分开分析

ts
var A // 定义了 和enum名称一样的全局变量 // 立即执行函数,对A赋值 (function (A) { ... })(A || (A = {})) // 如果A有值传入A(用于枚举合并的场景),否则传入兜底的空对象 // 对A键的赋值 A[A["Red"] = 0] = "Red"; // 分为两步分析 // 1. 包括 A["Red"] = 0 所以字符串类型的键名就获得了键值 // 2. 使用键值再进行赋值,实现反向映射,A[A["Red"] = 0] = "Red",相当于A[0] = "Red" // 而如果是字符串作为键值,只有这一层,没有反向映射 A["Red"] = "red";

4 type 和 interface

1 interface 语法

interface 语法,它成员有5种形式

  1. 对象属性,属性可以是可选的,只读的
ts
interface Point { x: number; y: number; }
  1. 对象属性索引
ts
interface A { [prop: string]: number; }
  1. 对象方法,类型方法可以重载
ts
// 写法一 interface A { f(x: boolean): string; } // 写法二 interface B { f: (x: boolean) => string; } // 写法三 interface C { f: { (x: boolean): string }; } // 方法重载 interface A { f(): number; f(x: boolean): boolean; f(x: string, y: string): string; }
  1. 函数,interface 也可以用于声明独立的函数
ts
interface Add { (x:number, y:number): number; } const myAdd:Add = (x,y) => x + y;
  1. 构造函数
ts
interface ErrorConstructor { new (message?: string): Error; }

2 interface 的继承

interface 可以继承其他类型,主要有以下几种情况

2.1 interface 继承 interface

语法

  • extends 关键字会从继承的接口里面拷贝属性类型,这样就不必书写重复的属性。
  • interface 允许多重继承。
ts
interface Style { color: string; } interface Shape { name: string; } interface Circle extends Style, Shape { radius: number; }

2.2 interface 继承 type

interface 可以继承type命令定义的对象类型

2.3 interface 继承 class

interface 还可以继承 class,即继承该类的所有成员

3 interface 的合并

多个同名接口会合并成一个接口

ts
interface Box { height: number; width: number; } interface Box { length: number; } // 两个`Box`接口会合并成一个接口,同时有`height`、`width`和`length`三个属性。

4 interface 和 type 的区别

interface 与 type 的区别有下面几点。 (1)type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。 (2)interface 可以继承其他类型,type 使用交叉类型实现继承,但是没有 extends 语法。 (3)同名 interface 会自动合并,同名 type 则会报错。也就是说,TypeScript 不允许使用 type 多次定义同一个类型 (4)interface 只能使用索引签名,type 可以使用索引签名和更灵活的映射类型 (5)this 关键字只能用于 interface (6)type 可以扩展原始数据类型,interface 不行。 (7)interface 无法表达某些复杂类型(比如交叉类型和联合类型),但是 type 可以。

综上所述,如果有复杂的类型运算,那么没有其他选择只能使用 type;一般情况下,interface 灵活性比较高,便于扩充类型或自动合并,建议优先使用。

5 ts 的 class

待补充

6 ts 的泛型

泛型(Generics)是 TypeScript 提供的一种工具,允许在定义函数、接口、类时不预先指定具体类型,而是在使用时再指定类型参数,从而实现类型的灵活复用

优势:

  • 类型安全:保证类型和使用需要的一致
  • 复用性高:一套代码可以处理多种类型,无需重复实现
  • 灵活性高:可以使用 泛型约束、默认值等

1 泛型的写法

ts 的泛型可以写在:函数、接口、类、类型别名上

1.1 函数的泛型写法

ts
// 写法一 function fn1<T>(args: T) { } // 写法二 let fn2 = <T>(args: T) => {}

1.2 接口的泛型写法

ts
interface Box<T> { prop: T } let a: Box<string>

比如 Array 的泛型写法

ts
const arr: Array<number> = [1,2,3]

接口的继承有一个示例,就是 compareTo 接口

ts
interface Comparator<T> { compareTo(value:T): number; } class Rectangle implements Comparator<Rectangle> { compareTo(value:Rectangle): number { // ... } }

1.3 类的泛型写法

这个和接口的写法差不多

ts
class Pair <K, V> { key: K; value: V; }

注意,泛型类描述的是类的实例,不包括静态属性和静态方法,因为这两者定义在类的本身。因此,它们不能引用类型参数。

ts
class C<T> { static data: T; // 报错 constructor(public value:T) {} }

1.4 类型别名的泛型写法

比如:本质还是和接口的泛型写法差不多

ts
type Tree<T> = { value: T; left: Tree<T> | null; right: Tree<T> | null; };

2 泛型的默认值

如果没有给 <T> 写类型,就会使用默认类型,但是默认类型会进行类型推导,实际参数类型会覆盖默认类型

ts
function fn<T = string>(args: T[]): T { return args[0] } const r1 = fn(123) // 报错 const r2 = fn([123]) // 不报错,因为进行了类型推导,使用了实际类型

一旦有了默认值,就表示为可选参数,多个参数时,可选参数需要在必选参数之后

ts
<T = boolean, U> // 错误 <T, U = boolean> // 正确

3 泛型的约束条件

比如,传入的参数必须满足有 length 属性,才能比较,否则会报错

ts
function comp<T extends { length: number }>( a: T, b: T ) { if (a.length >= b.length) { return a; } return b; }

泛型的类型参数可以同时设置约束和默认参数

ts
type Fn<A extends string, B extends string = "world"> = (a: A, b: B) => void; const fn: Fn<"hello"> = (a, b) => {}; fn("hello", "world");

如果有多个类型参数,一个类型参数的约束条件,可以引用其他参数,但是不能引用自身

ts
<T, U extends T> // 或者 <T extends U, U>

这个可以比较好的一个例子是,手写 ts 工具函数

ts
type CustomPick<T, K extends keyof T> = { [P in K]: T[K]; };

4 注意点

(1)尽量少用泛型。

泛型虽然灵活,但是会加大代码的复杂性,使其变得难读难写。一般来说,只要使用了泛型,类型声明通常都不太易读,容易写得很复杂。因此,可以不用泛型就不要用。

(2)类型参数越少越好。

多一个类型参数,多一道替换步骤,加大复杂性。因此,类型参数越少越好。

(3)类型参数至少需要出现两次。

如果类型参数在定义后只出现一次,那么很可能是不必要的。

(4)泛型可以嵌套。

类型参数可以是另一个泛型。

7 模块

ts 支持让模块导出类型,比如

ts
// 第一种写法 export type Bool = true | false; // 第二种写法 type Bool = true | false; export { Bool };

1 import export type 语句

在 ts 模块中,可以同时输入类型和正常接口,但是不利于区分类型和正常接口 解决方法有:

  1. 在 import 语句输入的类型前面加上 type 关键字
ts
import { type A, a } from './a';
  1. 使用 import type 语句,这个语句只用来输入类型,不用来输入正常接口
ts
// 可以输入类型 import type { A } from './a'; let b:A = 'hello'; // 输入正常接口会报错 import type { a } from './a'; let b = a;

同样的,export 语句也有两种方法,表示输出的是类型

ts
type A = 'a'; type B = 'b'; // 方法一 export {type A, type B}; // 方法二 export type {A, B};

可以使用 importsNotUsedAsValues 编译选项

  • remove:这是默认值,自动删除输入类型的 import 语句。
  • preserve:保留输入类型的 import 语句。
  • error:保留输入类型的 import 语句(与 preserve 相同),但是必须写成 import type 的形式,否则报错。

2 commonjs 模块

2.1 import = 语句

ts 使用 import = 语句输入 commonjs 模块

import = 和常见的 const = 是一样的

ts
import fs = require('fs'); const code = fs.readFileSync('hello.ts', 'utf8');

同时还兼容 import * as from 语法

ts
import * as fs from 'fs' // 等同于 import fs = require('fs')

2.2 export = 语句

TypeScript 使用 export = 语句,输出 CommonJS 模块的对象,等同于 CommonJS 的 module.exports 对象

使用 export = 语句输出的 commonjs 模块,只能使用 import = 导入

ts
let obj = { foo: 123 }; export = obj;

2.3 模块定位

模块定位(module resolution)指的是一种算法,用来确定 import 语句和 export 语句里面的模块文件位置。

默认值

  • node: modulecommonjs 时,moduleResolution 默认值为 node,即采用 Node.js 的模块定位算法
  • classic: module 设为 es2015、 esnext、amd, system, umd 等等时,采用 Classic 定位算法
  • bundler: 写相对路径时,不用补充拓展名
  • nodenextnode16

这一段可以直接看文档:

TypeScript 模块 - TypeScript 教程 - 网道

2.3.1 相对模块、非相对模块

加载模块时,目标模块分为相对模块(relative import)和非相对模块两种(non-relative import)

  • 相对模块指的是路径以 /./../ 开头的模块
  • 非相对模块指的是不带有路径信息的模块

非相对模块的定位,是由baseUrl属性或模块映射而确定的,通常用于加载外部模块

2.3.2 classic 方法

TypeScript 模块 - TypeScript 教程 - 网道

2.3.3 node 方法

Node 方法就是模拟 Node. js 的模块加载方法,也就是 require() 的实现方法

TypeScript 模块 - TypeScript 教程 - 网道

2.3.4 路径映射

  1. baseUrl: 可以手动指定脚本模块的基准目录
  2. paths: 指定非相对路径的模块与实际脚本的映射,常见的用来设置 @@components 等路径的映射
  3. rootDirs: 指定模块定位时必须查找的其他目录

TypeScript 模块 - TypeScript 教程 - 网道

8 装饰器

装饰器(Decorator)是一种语法结构,用来在定义时修改类(class)的行为。

在语法上,装饰器有如下几个特征。 (1)第一个字符(或者说前缀)是@,后面是一个表达式。 (2)@后面的表达式,必须是一个函数(或者执行后可以得到一个函数)。 (3)这个函数接受所修饰对象的一些相关值作为参数。 (4)这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象。

1 装饰器版本

装饰器分为

  • 标准语法
  • 传统语法

2 装饰器的结构

装饰器是一个函数,使用时接收两个参数

  • value:所装饰的对象
  • context:上下文对象,根据所装饰对象的不同而不同,其中只有两个属性(kind``和name)是必有的,其他都是可选的。
    • kind:字符串,表示所装饰对象的类型,可能取以下的值
    • name:字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等。
    • addInitializer():函数,用来添加类的初始化逻辑。
    • private:布尔值,表示所装饰的对象是否为类的私有成员。
    • static:布尔值,表示所装饰的对象是否为类的静态成员。
    • access:一个对象,包含了某个值的 get 和 set 方法。

3 装饰器的分类

3.1 类装饰器

3.2 方法装饰器

3.3 属性装饰器

3.4 gettter、setter 装饰器

3.5 accessor 装饰器

4 装饰器的执行顺序

装饰器的执行分为两个阶段。 (1)评估(evaluation):计算@符号后面的表达式的值,得到的应该是函数。 (2)应用(application):将评估装饰器后得到的函数,应用于所装饰对象。

TypeScript 装饰器 - TypeScript 教程 - 网道

9 declare 关键字

declare 关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用

declare 可以描述以下类型

  • 变量(const、let、var 命令声明)
  • type 或者 interface 命令声明的类型
  • class
  • enum
  • 函数(function)
  • 模块(module)
  • 命名空间(namespace)
  • global 全局

感觉一般常用与,全局声明的变量、函数、类等等,比如:document、或者其他引入的库

declare 关键字 - TypeScript 教程 - 网道

10 d.ts 类型声明文件

单独使用的模块,一般会同时提供一个单独的类型声明文件(declaration file),把本模块的外部接口的所有类型都写在这个文件里面,便于模块使用者了解接口,也便于编译器检查使用者的用法是否正确

类型声明文件也可以包括在项目的 tsconfig.json 文件里面,这样的话,编译器打包项目时,会自动将类型声明文件加入编译,而不必在每个脚本里面加载类型声明文件

ts
{ "compilerOptions": {}, "files": [ // 或者使用include "src/index.ts", "typings/moment.d.ts" ] }

1 类型声明文件的来源

类型声明文件主要有以下三种来源。

  • TypeScript 编译器自动生成。
  • TypeScript 内置类型文件。
  • 外部模块的类型声明文件,需要自己安装。

1.1 自动生成

只要使用编译选项 declaration,编译器就会在编译时自动生成单独的类型声明文件。

1.2 内置声明文件

之后再补充

11 类型运算符

1 keyof 运算符

keyof 是一个单目运算符,接受一个对象类型作为参数,返回该对象的所有键名组成的联合类型

注意:由于 JavaScript 对象的键名只有三种类型,所以对于任意对象的键名的联合类型就是 string|number|symbol

ts
// string | number | symbol type KeyT = keyof any;

2 typeof 运算符

TypeScript 中的 typeof 运算符接受一个值作为参数,返回该值的 TypeScript 类型

JavaScript 的 typeof 遵守 JavaScript 规则,TypeScript 的 typeof 遵守 TypeScript 规则。它们的一个重要区别在于,编译后,前者会保留,后者会被全部删除。

可以在 https://www.typescriptlang.org/play/ 尝试一下

3 in 运算符

TypeScript 中的 in 运算符用来取出联合类型的每一个成员类型

ts
type U = 'a'|'b'|'c'; type Foo = { [Prop in U]: number; }; // 等同于 type Foo = { a: number, b: number, c: number };

一个灵活用法,比如手写 Optional、Pick 类型工具

ts
type Optional<T> = { [P in keyof T]?: T[P]; }; type Pick<T, K extends keyof T> = { [P in K]: T[P]; };

4 方括号运算符

方括号运算符([])用于取出对象的键值类型,比如 T[K] 会返回对象T的属性K的类型。

ts
type Person = { age: number; name: string; alive: boolean; }; // number|string type T = Person['age'|'name']; // number|string|boolean type A = Person[keyof Person];

上面的手写工具函数中,就使用到了方括号运算符

注意:方括号中不能有值的运算

5 extend...?:条件运算符

条件运算符 extends...?: 可以根据当前类型是否符合某种条件,返回不同的类型。

ts
T extends U ? X : Y

比如

ts
// true type T = 1 extends number ? true : false;

6 infer 关键字

infer 关键字用来定义泛型里面推断出来的类型参数,而不是外部传入的类型参数。 它通常跟条件运算符一起使用,用在 extends 关键字后面的父类型之中。

比如

ts
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type; // string type Str = Flatten<string[]>; // number type Num = Flatten<number>;

7 is 运算符

is 运算符用于描述函数的返回值类型,写法采用parameterName is Type的形式,即左侧为当前函数的参数名,右侧为某一种类型。它返回一个布尔值,表示左侧参数是否属于右侧的类型。

函数返回布尔值的时候,可以使用is运算符,限定返回值与参数之间的关系。

is运算符用来描述返回值属于 true 还是 false

ts
function isFish( pet: Fish|Bird ):pet is Fish { // 这里使用 is 运算符,当pet 是Fish 的时候类型是true return (pet as Fish).swim !== undefined; }

8 satisfies 运算符

satisfies 运算符用来检测某个值是否符合指定类型

看文档这个例子即可: TypeScript 类型运算符 - TypeScript 教程 - 网道

12 类型守卫(类型保护)、类型谓词、类型断言、类型兼容

1 类型守卫(类型保护)

TypeScript 类型守卫(或者叫类型保护)(Type Guards)是一种在运行时检查变量类型的机制,它可以帮助 TypeScript 在特定的代码块中缩小变量的类型范围

类型守卫包括如下几种

  • typeof 类型守卫
ts
// 1. typeof 类型守卫 function printValue(value: string | number | boolean): void { if (typeof value === "string") { console.log(`字符串: ${value.toUpperCase()}`); } else if (typeof value === "number") { console.log(`数字: ${value.toFixed(2)}`); } else { console.log(`布尔值: ${value ? "真" : "假"}`); } }
  • instanceof 类型守卫
ts
// 2. instanceof 类型守卫 class Animal { constructor(public name: string) {} } class Dog extends Animal { bark() { console.log("汪汪!"); } } class Cat extends Animal { meow() { console.log("喵!"); } } function makeSound(animal: Animal): void { if (animal instanceof Dog) animal.bark(); else if (animal instanceof Cat) animal.meow(); }
  • 使用自定义类型守卫 (使用类型谓词)
ts
// 3. 自定义类型守卫 interface Bird { fly(): void; name: string; } interface Fish { swim(): void; name: string; } function isFish(pet: Bird | Fish): pet is Fish { return (pet as Fish).swim !== undefined; } function move(pet: Bird | Fish): void { if (isFish(pet)) pet.swim(); else pet.fly(); }
  • in 操作符类型守卫
ts
// 4. in 操作符类型守卫 interface Square { kind: 'square'; size: number; } interface Rectangle { kind: 'rectangle'; width: number; height: number; } function calculateArea(shape: Square | Rectangle): number { return 'size' in shape ? shape.size * shape.size : shape.width * shape.height; }
  • 字面量类型守卫
ts
// 5. 字面量类型守卫 type Direction = 'north' | 'south' | 'east' | 'west'; function move2(direction: Direction): void { switch (direction) { case 'north': console.log('向北移动'); break; case 'south': console.log('向南移动'); break; case 'east': console.log('向东移动'); break; case 'west': console.log('向西移动'); break; default: const exhaustiveCheck: never = direction; throw new Error(`未处理的方向: ${exhaustiveCheck}`); } }

好处

  • 增加代码的类型安全
  • 减少类型断言的使用

2 类型谓词

类型谓词(Type Predicates)是 TypeScript 中的一种特殊返回类型注解,它用于自定义类型守卫函数

类型谓词的形式为 parameterName is Type,其中 parameterName 必须是当前函数的参数名

比如下面代码

ts
// 自定义类型守卫处理可能为null/undefined的值 function isNonNullish<T>(value: T | null | undefined): value is T { return value !== null && value !== undefined; } // 应用示例 const values = ['a', 'b', null, 'c', undefined]; const nonNullValues = values.filter(isNonNullish); // 类型为 string[]

3 类型断言

注意:ts 中的类型转换概念一般可用指类型断言,只有在 js 中才有类型转换概念

TypeScript 提供了“类型断言”这样一种手段,允许开发者在代码中“断言”某个值的类型,告诉编译器此处的值是什么类型。

TypeScript 一旦发现存在类型断言,就不再对该值进行类型推断,而是直接采用断言给出的类型。(断言不是万能的,它会先检查断言类型是否能够兼容,不能兼容的话会报错)

比如

ts
interface Duck { name: string; age: number; city: string; } interface Bowl { price: number; } const bowl: Bowl = { price: 100, }; // Error 类型错误,Bowl 和 Duck 是完全不能兼容的,这种情况不允许变型,同时也不能断言 const duck: Duck = bowl as Duck;

4 类型兼容

类型兼容性用于确定一个类型是否能赋值给其他类型,ts 中判断类型是否能够兼容,会通过四种方式判断,分别是

  • 协变
  • 逆变
  • 双向协变
  • 不变(即不能类型兼容,此时会报错)

5 协变、逆变、双向协变、不变

5.1 子类型和可赋值性

子类型

  • 父类型是属性更少的一方,子类型继承父类型所有属性的同时,有新的属性

参考集合论中,父集和子集的关系即可,父类型属性更少,属于子集;子类型属性更多,属于父集。

注意:父类型相当于 子集 而不是父集,因为他的属性更少

可赋值性

范围更大的类型可以赋值给范围更小的类型,也就是子类型可以赋值给父类型,而父类型不能赋值给子类型

实际应用

ts
// 比如使用泛型的场景,extends约束了,T必须包含a function fn<T extends { a: string }>(args: T) { return args.a; } fn({ a: "123", b: 123 }); fn({ b: 123 }); // 报错

5.2 协变

注意:这里的 T 是类型构造器,比如这里的普通类型定义

如果 A 是 B 的子类型,那么 T<A> 也是 T<B> 的子类型,这种情况就叫做协变 TypeScript 中数组和对象的属性是协变的

ts
// 假设已经定义了 Dog 和 Animal,这个在很多教程中都是一样的 // 数组的协变 let animal: Animal[] = [] let dog: Dog[] = [] animal = dog // 协变,animal 可以兼容 dog // 对象的协变 let obj1 = { prop: Dog // 仅用来表示类型 } let obj2 = { prop: Animal } obj2 = obj1

5.3 逆变

注意:这里的 T 是类型构造器,比如这里的函数类型定义

如果 A 是 B 的子类型,那么 T<B> 反而是 T<A> 的子类型,这种情况就叫做逆变 TypeScript 中函数参数是逆变的

ts
// 逆变 // T<Animal> var getAnimal = function (animal: Animal): void { console.log(animal.name); }; // T<Dog> var getDog = function (dog: Dog): void { console.log(dog.name); }; getAnimal = getDog; // X getDog = getAnimal; // 这里类型逆变了

5.4 双向协变

在 TypeScript 中,由于灵活性等权衡,对于函数参数默认的处理是 双向协变 的。也就是既可以 visitAnimal = visitDog,也可以 visitDog = visitAnimal。在开启了 tsconfig 中的 strictFunctionType 后才会严格按照 逆变 来约束赋值关系。

5.5 不变

不变就是不允许变型。如果两个类型完全不相同,它们是不能兼容的。

ts
interface Duck { name: string; age: number; city: string; } interface Bowl { price: number; } const bowl: Bowl = { price: 100, }; // Error 类型错误,Bowl 和 Duck 是完全不能兼容的,这种情况不允许变型 const duck: Duck = bowl;

参考资料

13 索引签名和类型映射

1 索引签名

索引签名可以让对象可以具有任意数量的属性,只要属性名和值符合指定的类型即可

在 TypeScript 中,我们可以使用 string 、number 和 symbol 作为索引签名

比如:

ts
type K = { [name: string | number]: string; }; const k: K = { x: 'x', 1: 'b' };

2 类型映射

类型映射是指基于一个现有类型创建新类型,通过遍历现有类型的属性来转换它们

比如:

ts
// 类型映射示例 interface Person { name: string; age: number; } // 将所有属性变为可选 type PartialPerson = { [K in keyof Person]?: Person[K]; } // 将所有属性变为只读 type ReadonlyPerson = { readonly [K in keyof Person]: Person[K]; }

2.1 映射修饰符

TypeScript 引入了两个映射修饰符,用来在映射时添加或移除某个属性的 ? 修饰符和 readonly 修饰符

  • +修饰符:写成+?+readonly,为映射属性添加?修饰符或readonly修饰符。
  • 修饰符:写成 -?-readonly,为映射属性移除 ? 修饰符或 readonly 修饰符。

比如

ts
// 添加可选属性 type Optional<Type> = { [Prop in keyof Type]+?: Type[Prop]; }; // 移除可选属性 type Concrete<Type> = { [Prop in keyof Type]-?: Type[Prop]; };
ts
// 添加 readonly type CreateImmutable<Type> = { +readonly [Prop in keyof Type]: Type[Prop]; }; // 移除 readonly type CreateMutable<Type> = { -readonly [Prop in keyof Type]: Type[Prop]; };

2.2 键名重映射

TypeScript 4.1 引入了键名重映射,允许改变键名。 语法为

ts
{ [P in K as 新类型] // as 可以修改映射的键名 }

实际的应用比如:定义getter/setter 函数

ts
interface Person { name: string; age: number; location: string; } type Getters<T> = { [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]; }; type LazyPerson = Getters<Person>; // 等同于 type LazyPerson = { getName: () => string; getAge: () => number; getLocation: () => string; }

2.2.1 属性过滤

比如,可以 as 写一个 Filter 工具

ts
type Filter<T, K extends keyof T> = { [P in keyof T as P extends K ? never : P]: T[P]; }; type A = { a: string, b: number, c: boolean } /** * { * c: boolean * } */ type FA = Filter<A, 'a' | 'b'>

14 类型工具

1 Promise

1.1 Awaited

Awaited<Type> 用来取出 Promise 的返回值类型,适合用在描述 then() 方法和 await 命令的参数类型。

ts
// string type A = Awaited<Promise<string>>;

2 联合类型

2.1 Exclude(对应 Extract)

Exclude 用来从联合类型里,删除某些类型,组成一个新的类型返回

ts
type T1 = Exclude<'a'|'b'|'c', 'a'>; // 'b'|'c'

2.2 NonNullable

从联合类型中删除 nullundefined 类型返回,暂时没看出用处

ts
type A = NonNullable<string | null | undefined>; // string

3 对象

3.1 InstanceType

InstanceType<Type> 提取构造函数的返回值的类型(即实例类型),参数 Type 是一个构造函数,等同于构造函数的 ReturnType<Type>

3.2 Omit

从对象类型中删除一些键

3.3 Partial(对应 Required)

让对象类型所有键变为可选

3.4 Pick

从对象类型中选出一些键作为新的类型

3.5 Readonly

将所有属性转为只读属性

3.6 Record

Record 返回一个对象类型

用法如下

ts
// 1. // { a: number } type T = Record<'a', number>; // 2. // { a: number, b: number } type T = Record<'a'|'b', number>; // 3. // { a: number|string } type T = Record<'a', number|string>;

4 函数

4.1 Parameters

Parameters<Type> 从函数类型 Type 里面提取参数类型,组成一个元组返回。

4.2 RetureType

ReturnType<Type> 提取函数类型 Type 的返回值类型,作为一个新类型返回。

15 注释指令

  1. // @ts-nocheck 告诉编译器不对当前脚本进行类型检查,可以用于 TypeScript 脚本,也可以用于 JavaScript 脚本。
  2. 如果一个 JavaScript 脚本顶部添加了 // @ts-check,那么编译器将对该脚本进行类型检查,不论是否启用了 checkJs 编译选项。
  3. // @ts-ignore 告诉编译器不对下一行代码进行类型检查,可以用于 TypeScript 脚本,也可以用于 JavaScript 脚本。
  4. // @ts-expect-error 主要用在测试用例,当下一行有类型错误时,它会压制 TypeScript 的报错信息(即不显示报错信息),把错误留给代码自己处理。如果下一行没有报错,则会显示 Unused '@ts-expect-error' directive. 报错

16 tsconfig.json

TODO

17 TypeScript 编译原理

TODO

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:pepedd864

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!