TypeScript 文档
简洁、以示例为主的 TypeScript 参考文档。先读概念,再看代码,然后在 Coddy 学习旅程中动手练习。
开始课程入门
- TypeScript 是什么TypeScript 就是带静态类型的 JavaScript。你给值加上类型注解,编译器在代码运行前检查它们,最终输出的是普通 JavaScript,可以在任何浏览器以及 Node.js、Deno 和 Bun 中运行。
- TypeScript vs JavaScriptTypeScript 就是 JavaScript 加上一套在代码运行前检查的静态类型系统。本文把两者放在一起对比:语法、类型检查器能发现什么问题、构建步骤、运行速度、学习曲线,以及如何迁移 JavaScript 项目。
- 安装 TypeScript用 npm 把 TypeScript 安装为项目的开发依赖,用 npx tsc --version 查看版本,用 tsc --init 创建 tsconfig.json,再编译你的第一个文件。还介绍了全局安装、pnpm、Yarn 和 Bun,以及大家常遇到的错误。
- 运行 TypeScript运行 .ts 文件的五种方式:用 tsc 编译后运行生成的 JavaScript、用 node file.ts 直接运行(类型剥离)、使用 tsx 或 ts-node,以及使用 Deno 和 Bun。本文说明哪些方式会检查类型、各自支持哪些语法,以及该如何选择。
- tsconfig.jsontsconfig.json 把一个文件夹标记为 TypeScript 项目,并设置编译选项。本文讲解重要的选项(target、module、moduleResolution、strict、rootDir、outDir、include、lib、types、noEmit、skipLibCheck)、推荐的起步配置、extends,以及 TypeScript 7 带来的变化。
- TypeScript 7TypeScript 7 是用 Go 重写、以原生程序形式发布的 TypeScript 编译器:速度大约快十倍,命令仍是 tsc,语言也完全相同。本文介绍对使用者有哪些变化(新的默认值、被移除的选项、缺失的 JavaScript API)、哪些工具仍需要 TypeScript 6,以及如何升级。
- 注释TypeScript 使用 JavaScript 的 // 和 /* */ 注释,以及编辑器在悬停时显示的 JSDoc /** */ 注释。它还会读取几种特殊注释:@ts-expect-error、@ts-ignore、@ts-nocheck、@ts-check 和 /// <reference> 指令。
- TypeScript vs PythonTypeScript 和 Python 都是带垃圾回收的高级语言,但 TypeScript 在代码运行前检查类型,而 Python 的类型提示是可选的,运行时会被忽略。本文从类型、运行时、速度、生态和用途几个方面并排对比两者,并用两种语言写同一个程序。
基本类型
- 基础类型TypeScript 的内置类型:string、number、boolean、bigint、symbol、null 和 undefined,再加上数组和对象的概览。如何写类型注解,为什么没有整数类型,以及为什么要写 string 而不是 String。
- 类型推断TypeScript 能从你写的值推断出大部分类型。本文讲解它如何推断变量、let 与 const、对象、数组和返回值的类型,回调函数如何从上下文获得类型,以及哪些地方仍然需要类型注解。
- 字符串在 TypeScript 中处理字符串:用模板字面量做插值、多行字符串、判断字符串是否包含子串、常用方法及其返回类型,以及字符串字面量类型。
- 字符串转数字在 TypeScript 中用 Number()、parseInt()、parseFloat() 或一元 + 把字符串转换为数字,看看它们各自如何处理 "42px"、"" 和 "1e3" 这样的输入,如何安全地检查 NaN,以及如何把数字转回字符串。
- 数组TypeScript 中如何为数组标注类型:T[] 与 Array<T>、对象数组、包含多种类型的数组、只读数组,以及 map、filter、reduce 和 find 的返回类型。还有字面量数组上 includes 的陷阱。
- 元组TypeScript 元组是一个元素个数固定、每个位置类型已知的数组,例如 [string, number]。本文讲解元组语法、具名元素、可选元素和剩余元素、只读元组与 as const、从函数返回元组,以及元组和数组的区别。
- 对象类型如何在 TypeScript 中为对象标注类型:内联对象类型、用 ? 表示的可选属性、只读属性、嵌套对象、方法、多余属性检查,以及 object、{} 和 Object 的区别。
- 枚举TypeScript 枚举(enum)是一组具名常量,例如 enum Direction { Up, Down }。学习数字枚举和字符串枚举、枚举编译后的 JavaScript、反向映射、遍历枚举、const 枚举,以及什么时候改用字符串字面量联合类型或 as const 对象更合适。
特殊类型
- 字面量类型字面量类型是只有一个值的类型,比如 "GET" 或 404。本文讲解字符串、数字和布尔字面量类型,字面量联合类型,为什么 let 会拓宽类型而 const 不会,as const 的作用,以及 const 类型参数。
- any 与 unknownany 和 unknown 都能接受任何值。any 会关闭对这个值的类型检查,而 unknown 要求你先检查值,再使用它。了解两者的区别、如何收窄 unknown、noImplicitAny,以及 any 是怎样悄悄混进有类型的代码里的。
- never 类型never 是没有任何值的类型。它是永远不会结束的函数的返回类型,是类型收窄排除了所有情况后剩下的类型,也是 switch 穷尽检查背后的工具。本文讲解 never 从哪里来,以及它和 void 有什么不同。
- null 和 undefined开启 strictNullChecks 后,null 和 undefined 是独立的类型,TypeScript 会要求你处理它们。本文讲解如何检查它们、可选链(?.)、双问号(??)和 ??=,以及可选属性和 | undefined 的区别。
- 非空断言(!)值后面的感叹号(例如 user!)是非空断言运算符:它从类型中去掉 null 和 undefined,却不做任何运行时检查。本文讲解 x! 的作用、明确赋值断言 let x!: T 和 prop!: T、它们为什么有风险,以及更安全的替代写法。
接口与类型别名
- 接口接口为对象的结构命名:它有哪些属性,每个属性是什么类型。学习如何声明接口、可选和只读属性、方法、索引签名、继承、在类中实现、声明合并、泛型接口,以及如何给接口设置默认值。
- 类型别名类型别名用 type 关键字给任意类型起一个名字:对象形状、联合类型、元组、函数、泛型和递归类型都可以。本文讲解语法、每种形式的写法,以及为什么别名只是一个名字,而不是一个新的独立类型。
- interface 与 typeinterface 和 type 都能描述对象的结构,大多数时候用哪个都行。本文讲清两者真正的区别:声明合并、联合类型和映射类型、extends 与交叉类型、隐式索引签名、错误提示和编译器性能,并给出一条清晰的选择规则。
- extends 关键字extends 关键字用一个类型构建另一个类型。学习如何扩展接口(单个或多个)、用 & 扩展类型别名、覆盖属性类型、用 Omit 替换属性,以及 extends 在类、泛型约束和条件类型中的含义。
- 联合类型像 string | number 这样的联合类型表示一个值可以是几种类型中的任意一种。本文讲解联合类型能做什么(只能用所有成员都支持的操作)、如何收窄、字面量联合与对象联合,以及 (A | B)[] 和 A[] | B[] 的区别。
- 交叉类型交叉类型 A & B 描述的是同时属于 A 和 B 的值,所以它拥有两边的全部成员。本文讲解如何用 & 组合对象类型、冲突的属性为什么会变成 never、联合类型取交叉后为什么只保留共同的成员,以及什么时候应该改用 extends。
- 可辨识联合可辨识联合是一组共享同一个字面量标签属性(比如 kind 或 status)的对象类型组成的联合。检查这个标签就能收窄整个对象。学习这种模式、用 switch 收窄、用 never 做穷尽性检查,以及如何建模 API 结果、请求状态和状态机。
函数
- 函数如何在 TypeScript 中给函数标注类型:为每个参数加上标注,标注或推断返回类型,用 void 表示不返回任何值的函数,以及为箭头函数、函数表达式、async 函数和 this 参数标注类型。
- 函数类型如何在 TypeScript 中写出函数的类型:箭头语法 (a: number) => string、函数的类型别名、为箭头函数和回调标注类型、调用签名与构造签名,以及为什么 Function 类型太宽松。
- 可选参数用 ? 把 TypeScript 参数标记为可选,给参数设置默认值,或者用剩余参数接收任意数量的实参。本文涵盖参数顺序规则、省略与传 undefined 的区别、选项对象,以及函数类型中的可选参数。
- 函数重载TypeScript 函数重载让一个函数拥有多个调用签名,每个签名都有自己的返回类型。学习“重载签名加实现”的写法、编译器检查的规则、什么时候改用联合类型参数更好,以及类中的重载。
类型收窄与类型检查
- 类型收窄类型收窄是指 TypeScript 在 if、switch 中或提前返回之后,把 string | number 这样的宽类型变成一个具体类型。本文汇总了所有收窄方式:typeof、真值判断、相等判断、in、instanceof、赋值、类型谓词和可辨识联合。
- typeof 运算符typeof 在 TypeScript 中有两种用途。在代码中,它是 JavaScript 运算符,运行时返回 "string"、"number"、"object" 等字符串,TypeScript 会据此收窄类型。在类型中,它是类型查询,用来复制一个变量的类型,例如 keyof typeof obj 和 ReturnType<typeof fn>。
- instanceof 运算符instanceof 在运行时检查一个对象是否由某个类创建,TypeScript 会把变量收窄为这个类。它如何配合你自己的类和 Error 子类使用,为什么不能检查接口或类型别名,以及它在哪些地方会给出出人意料的结果。
- 类型守卫类型守卫是 TypeScript 能看懂的运行时检查。本文讲解内置的类型守卫、如何用 value is Type 类型谓词编写自己的守卫、如何检查对象是否属于某个类型、用 asserts 写断言函数,以及如何校验 unknown 数据。
- 类型断言as 关键字让 TypeScript 把一个值当作另一种类型来对待。它不是类型转换:运行时既不转换也不检查任何东西。本文讲解 as 和尖括号语法、编译器允许哪些断言、经由 unknown 的双重断言,以及什么时候类型守卫是更好的选择。
- satisfies 运算符satisfies 运算符检查一个值是否符合某个类型,同时不改变这个值的推断类型。本文讲解它的作用、它与类型注解和 as 的区别(同一个对象的三种写法)、如何与 as const 结合,以及它为什么适合配置对象。
类
- 类TypeScript 类就是带有类型的字段、方法和构造函数的 JavaScript 类。本文讲解字段声明和 strictPropertyInitialization 如何工作、如何为 this 标注类型、getter 和 setter、静态成员、implements,以及类为什么同时也是一个类型。
- 构造函数如何在 TypeScript 中为类的构造函数标注类型:带类型的参数和可选参数、constructor(private name: string) 这样的参数属性、字段初始化顺序、构造函数重载、子类中的 super 调用、私有构造函数,以及用 new 表示的构造函数类型。
- 访问修饰符TypeScript 有三个访问修饰符:public、private 和 protected,另外还有 readonly。本文讲清每个修饰符允许什么,为什么 TypeScript 的 private 只是编译时检查而 JavaScript 的 #private 字段在运行时强制生效,以及该如何选择。
- 继承TypeScript 中的类继承:extends 和 super,用兼容的类型重写方法,override 关键字与 noImplicitOverride,子类中的 protected 成员,用 declare 重新声明字段,以及什么时候 implements 更合适。
- 抽象类TypeScript 中的抽象类是一种不能被实例化的基类,可以把部分方法留给子类去实现。本文介绍抽象方法和抽象属性、模板方法模式、抽象构造函数类型,以及什么时候用接口更合适。
- 装饰器装饰器是用 @ 语法包装或替换类成员的函数。本文讲解 TypeScript 无需任何开关就支持的标准装饰器(类、方法、getter、字段和 accessor)、装饰器工厂、addInitializer,以及它们与 Angular 和 NestJS 使用的旧版 experimentalDecorators 有何不同。
泛型
类型运算符
- keyof 运算符keyof 接收一个对象类型,得到由它的属性名组成的联合类型。本文讲解 keyof 与接口、用于普通对象的 keyof typeof、结合泛型的类型安全属性访问、索引签名(string | number),以及 Object.keys 为什么返回 string[]。
- 索引访问类型索引访问类型从另一个类型中读取某个属性的类型:Person["age"] 是 number。学习 T["key"]、联合类型的键、T[keyof T]、获取数组元素类型的 T[number]、元组索引,以及用 (typeof arr)[number] 把 const 数组变成联合类型。
- 映射类型映射类型通过遍历一组键来构建新的对象类型:{ [K in keyof T]: ... }。本文讲解它的语法,带加号和减号的 readonly 与 ? 修饰符,用 as 重映射键,过滤键,以及 Partial、Readonly、Required、Pick 和 Record 是怎么写出来的。
- 条件类型条件类型根据一个判断在两个类型中选出一个:T extends U ? X : Y。本文讲解它的语法、条件类型如何在联合类型上分配(以及如何阻止分配)、用 infer 提取类型,以及如何自己实现 ReturnType。
- 模板字面量类型模板字面量类型用和 JavaScript 模板字符串相同的反引号语法来构建字符串字面量类型:`on${Capitalize<E>}`。本文讲解它的语法、联合类型如何相乘、Uppercase 与 Capitalize、`${number}px` 这类模式、映射类型生成的 getter,以及用 infer 解析字符串。
- 品牌类型品牌类型是带有隐形标签的原始类型,例如 string & { readonly __brand: "UserId" },这样在需要 OrderId 的地方就无法传入 UserId。本文讲解品牌的工作原理、负责校验的构造函数、通用的 Brand 辅助类型、unique symbol 品牌以及品牌化的数字。
工具类型
- 工具类型一页看全 TypeScript 所有内置工具类型:Partial、Required、Readonly、Pick、Omit、Record、Exclude、Extract、NonNullable、Parameters、ReturnType、Awaited、字符串工具类型等,每个都有一句话说明和可运行示例。
- RecordRecord<K, V> 是键为 K、所有值都为 V 的对象类型。本文讲解用 string 作键和用联合类型作键(每个键都必须存在)的 Record、Partial<Record>、以带类型的键遍历 Record、缺失键的陷阱,以及什么时候该改用索引签名或 Map。
- Partial 与 RequiredPartial<T> 把 T 的每个属性都变成可选,这正是更新对象或补丁对象的类型。本文讲解 Partial 在更新函数中的用法、为什么它是浅层的、如何编写 DeepPartial、显式 undefined 的陷阱,以及它的反面 Required<T>。
- OmitOmit<T, K> 会创建一个包含 T 中除键 K 以外所有属性的类型。本文讲解如何用 Omit 移除一个或多个键、覆盖属性类型、在运行时删除属性、Omit 与 Exclude 和 Pick 的区别、为什么 Omit 接受不存在的键、严格版 Omit,以及联合类型上的 Omit。
- PickPick<T, K> 会创建一个只包含 T 中键在 K 里的属性的类型。本文讲解如何用 Pick 选取一个或多个键、它如何检查键、Pick 与 Omit 的区别、从嵌套类型中选取、带类型的 pick() 函数,以及按值类型选取属性。
- readonly 与 Readonlyreadonly 修饰符和 Readonly<T> 工具类型可以阻止代码重新给属性赋值。本文讲解只读属性和类的只读字段、Readonly<T>、只读数组(readonly T[] 和 ReadonlyArray)、ReadonlyMap 和 ReadonlySet,readonly 为什么是浅层的、只在编译时生效,以及它和 Object.freeze、as const 的比较。
- Exclude 与 ExtractExclude、Extract 和 NonNullable 用来筛选联合类型的成员。了解它们各自保留和移除什么,如何按结构挑选联合类型成员,它们如何由条件类型构建,以及它们和 Omit、Pick 有什么区别。
- ReturnType 与 ParametersReturnType、Parameters、ConstructorParameters、InstanceType 和 Awaited 用来从函数、类和 Promise 中提取类型。本文讲解如何配合 typeof 使用它们、如何获取 async 函数的结果类型、遇到重载和泛型时会发生什么,以及 ReturnType 是如何用 infer 实现的。
循环与集合
- 循环TypeScript 中的每一种循环写法,以及各自带来的类型:经典 for 循环,遍历数组、Map 和字符串的 for...of,键总是字符串的 for...in,forEach(不能 break,也不能 await),按类型遍历对象的键,以及 while。
- switch 语句TypeScript 中的 switch 语句:语法、每个 case 如何收窄联合类型、漏掉 case 时会编译失败的穷尽式 switch、switch (true) 写法、贯穿(fallthrough)以及块级作用域。
- MapTypeScript 中 Map 的用法:创建带类型的 Map<K, V>,为什么 get 返回 V | undefined,set、has 和 delete,按插入顺序遍历,以对象作键,与对象和 JSON 互相转换,Map、对象与 Record 的对比,以及 array.map() 的类型。
- 字典TypeScript 没有字典或哈希表类;你可以用索引签名、Record<K, V> 或 Map<K, V> 为键值查找标注类型。本文逐一讲解这三种方式,如何判断键是否存在、如何添加、删除和遍历,以及为什么 noUncheckedIndexedAccess 对字典很重要。
异步与错误
- PromiseTypeScript 中如何为 Promise 标注类型:Promise<T> 类型、为 new Promise 和 resolve 标注类型、then 如何改变类型、为什么 catch 给你的是 any、返回元组结果的 Promise.all、Promise.allSettled 的结果类型,以及把回调式 API 包装成带类型的 Promise。
- Async/AwaitTypeScript 中 async 和 await 的类型:async 函数返回 Promise<T>,await 把它解包,错误用 try/catch 捕获,顶层 await 需要 ES 模块,以及逐个 await、并行 await 的区别和 forEach 的陷阱。
- 错误处理TypeScript 中的错误处理:为什么 catch 变量是 unknown,如何用 instanceof Error 收窄它,如何抛出错误,如何编写带 name 和 cause 的自定义错误类,以及处理预期错误的 Result 类型模式。
- SleepTypeScript 没有内置的 sleep,但一行代码就能写出一个:返回 Promise<void>、由 setTimeout 来 resolve 的函数。本文讲解如何 await 它、在循环中暂停、带延迟的重试、取消 sleep,以及为什么 JavaScript 里没有阻塞式的 sleep。
模块与工具
- 模块每个带有顶层 import 或 export 的 TypeScript 文件都是一个模块。本文讲解命名导出和默认导出,import type 与 export type,module 设置如何决定输出 ES 模块还是 CommonJS,以及为什么 node16 和 nodenext 要求导入路径带 .js 扩展名。
- 命名空间TypeScript 命名空间把值和类型归到一个名称下,编译后是一个普通对象。本文讲解它的语法,命名空间之间以及与函数和类的合并,为什么 ES 模块取代了它,以及你仍会遇到它的地方:声明文件和全局扩充。
- 声明文件.d.ts 文件描述 JavaScript 代码的类型,却不包含任何代码本身,而 declare 关键字在 .ts 文件中起同样的作用。本文讲解声明文件如何生成、@types 包的作用、如何为没有类型的模块编写类型,以及 declare global 和模块扩充如何扩展已有类型。
- 严格模式tsconfig.json 中的 strict: true 会开启一组类型检查:noImplicitAny、strictNullChecks、strictPropertyInitialization 以及另外五个。本文讲解每一项能捕获什么、如何在现有项目中开启严格模式,以及 strict 不包含的实用选项。
- JSONJSON.parse 返回 any,所以你给结果标注什么类型,TypeScript 就相信什么类型。本文讲解如何为解析后的 JSON 标注类型、用类型守卫校验它、把 JSON 示例转换成接口、导入 .json 文件,以及 JSON.stringify 会如何处理 Date、Set 和 undefined。