← 返回文章列表
TypeScript 全指南:从入门到精通
基于费曼学习法、西蒙学习法、SQ3R 阅读法和康奈尔笔记法,系统掌握 TypeScript 的核心概念与实战技巧。
一、概览与提问(SQ3R · Survey & Question)
SQ3R 第一步:快速浏览全貌,提出关键问题。
什么是 TypeScript?
TypeScript 是由微软开发并维护的开源编程语言,它是 JavaScript 的严格超集——这意味着任何合法的 JavaScript 代码都是合法的 TypeScript 代码。TypeScript 在 JavaScript 的基础上添加了可选的静态类型系统和最新的 ECMAScript 特性支持,最终编译回普通的 JavaScript 代码运行。
TypeScript 于 2012 年首次发布,由 C# 的首席架构师 Anders Hejlsberg 领导开发。它诞生的背景很明确:随着 JavaScript 应用的规模和复杂度不断增长,动态类型系统的短板日益暴露——大型代码库中缺乏编译时错误检查、IDE 支持有限、重构困难。TypeScript 通过在开发阶段引入类型注解和静态分析来解决这些问题,同时保持了 JavaScript 的灵活性和生态系统兼容性。
核心问题
- TypeScript 适合什么场景?——中大型项目、团队协作开发、需要长期维护的代码库、以及任何追求代码可靠性的场景。
- TypeScript 与同类技术相比有什么优势?——相比 Flow 或 JSDoc 类型注解,TypeScript 拥有更强大的类型系统、更活跃的社区、更好的工具链支持,以及与主流框架(React、Vue、Angular)的一等公民集成。
- 学习 TypeScript 需要什么基础?——扎实的 JavaScript 知识(ES6+),对面向对象编程和函数式编程的基本了解会有帮助,但不是必须的。
技术全景图
TypeScript 的核心架构可以理解为三层结构:
基础层:类型系统——包含原始类型(string、number、boolean、null、undefined、symbol、bigint)、对象类型(interface、type alias)、联合类型、交叉类型、字面量类型等。这是构建一切类型表达的基础。
中间层:类型操作——泛型(Generics)让类型可以参数化;条件类型(Conditional Types)让类型可以像 if-else 一样做判断;映射类型(Mapped Types)可以批量转换类型的属性;模板字面量类型(Template Literal Types)可以在类型层面操作字符串。这些工具组合起来,能在编译时完成复杂的类型计算。
顶层:工程实践——包括 tsconfig.json 配置、声明文件(.d.ts)、模块系统、装饰器(Decorators)、与构建工具(Webpack、Vite、esbuild)的集成,以及日常开发中的最佳实践。
二、用最简单的话说清楚(费曼学习法)
费曼学习法核心理念:如果你不能用简单的语言解释一件事,说明你还没有真正理解它。
核心概念讲解
类型注解(Type Annotations)
类型注解就是告诉 TypeScript:"这个变量是什么类型的东西"。就像在图书馆给书贴标签一样——贴上"科幻小说"标签,你就知道这本书属于哪个分类。
let name: string = "TypeScript";
let version: number = 6.0;
let isOpenSource: boolean = true;接口(Interfaces)
接口是一份"契约"或"蓝图",定义了一个对象应该有哪些属性和方法。任何想履行这份契约的对象,都必须提供接口要求的所有东西。
interface User {
name: string;
age: number;
greet(): string;
}
const user: User = {
name: "Alice",
age: 30,
greet() {
return `Hello, I'm ${this.name}`;
},
};联合类型(Union Types)
联合类型表示一个值可以是几种类型中的任意一种。用 | 符号连接,意思是"或者"。
let id: string | number;
id = "abc-123"; // OK
id = 42; // OK泛型(Generics)
泛型就是给类型加"占位符"。就像表格模板里的空白栏——你在使用时才填入具体内容。这让函数和类可以处理多种类型,同时保持类型安全。
function identity<T>(value: T): T {
return value;
}
const result1 = identity<string>("hello"); // 类型是 string
const result2 = identity<number>(42); // 类型是 number类型守卫(Type Guards)
类型守卫是一种运行时检查,帮助 TypeScript 在特定代码块中"收窄"变量的类型。就像保安检查你的证件——通过检查后,就知道你是谁了。
function processValue(value: string | number) {
if (typeof value === "string") {
// 在这个分支里,TypeScript 知道 value 是 string
console.log(value.toUpperCase());
} else {
// 在这个分支里,TypeScript 知道 value 是 number
console.log(value.toFixed(2));
}
}类型推断(Type Inference)
TypeScript 很聪明,很多时候你不需要手动写类型——它会根据上下文自动推断。就像你走进一家咖啡店说"来一杯",店员能根据你的习惯推断你要美式。
// TypeScript 自动推断 x 的类型为 number
let x = 10;
// 自动推断返回值类型为 string
function greet(name: string) {
return `Hello, ${name}`;
}类比与比喻
把 TypeScript 的类型系统想象成一个快递分拣中心:
- 原始类型(
string、number、boolean)就像标准化的包裹尺寸——小件、中件、大件,每种都有固定的规格。 - 接口和类型别名就像是定制的包装盒模板——你可以定义盒子需要多少格子、每个格子放什么。
- 联合类型就像"可以放小件或中件"的弹性格口——两种都能放,但必须属于其中一种。
- 泛型就像可调节的货架——根据当天要存放的货物类型来调整货架尺寸,保证每种货物都有合适的空间。
- 类型守卫就像分拣员扫描条形码——扫描之后,就确定了包裹的具体类型,可以放到对应的格口。
- 条件类型就像自动化的分拣规则——"如果是易碎品,贴上'轻拿轻放'标签;否则贴上'普通件'标签"。
常见误解澄清
-
"TypeScript 是另一种语言,需要重新学"——不对。TypeScript 是 JavaScript 的超集,你的 JS 知识完全适用。你可以渐进式地引入类型,不需要一次性改写所有代码。
-
"TypeScript 让代码更臃肿"——类型注解只占很少的代码量,换来的是编译时错误捕获和更好的 IDE 支持。在大型项目中,这种投入产出比非常划算。
-
"
any类型就是不用 TypeScript"——虽然any绕过了类型检查,但合理使用unknown或泛型往往更好。any是逃生舱,不是日常工具。 -
"interface 和 type 完全一样"——它们大部分场景可互换,但 interface 支持声明合并(declaration merging),type 支持联合类型和交叉类型的内联定义。选择哪一个取决于具体需求。
-
"TypeScript 只适合大项目"——即使是小项目,类型系统也能帮你捕获拼写错误、遗漏属性等问题。许多开发者发现,一旦习惯 TS 的开发体验,就很难回到纯 JS 了。
三、锥形深入(西蒙学习法)
西蒙学习法:集中精力、目标导向、锥形深入——从核心开始,逐步扩展到周边。
第一层:核心基础
3.1 基础类型
TypeScript 的基础类型与 JavaScript 的原始类型一一对应,并增加了 void、never、unknown 等特殊类型。
// 原始类型
let str: string = "hello";
let num: number = 42;
let bool: boolean = true;
let n: null = null;
let u: undefined = undefined;
let sym: symbol = Symbol("id");
let big: bigint = 100n;
// 特殊类型
let notSure: unknown = "maybe a string";
notSure = 42; // OK,unknown 可以接收任何值
let nothing: void = undefined; // 常用于函数没有返回值的场景
let impossible: never; // 永远不会有值的类型
// 数组
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];
// 元组——固定长度和类型的数组
let tuple: [string, number] = ["age", 25];3.2 接口(Interfaces)
接口描述了对象的"形状"——有哪些属性、什么类型。
interface Person {
name: string;
age: number;
email?: string; // 可选属性
readonly id: number; // 只读属性
}
const person: Person = {
name: "Bob",
age: 25,
id: 1,
};
// person.id = 2; // 错误:id 是只读的
// 接口可以扩展
interface Employee extends Person {
department: string;
salary: number;
}3.3 类型别名(Type Aliases)
type 关键字给一个类型起一个名字,比 interface 更灵活。
// 基础用法
type ID = string | number;
type Point = { x: number; y: number };
// 与 interface 的区别:type 可以表示联合类型
type Status = "active" | "inactive" | "suspended";
// 也可以表示函数类型
type Callback = (data: string) => void;
// 交叉类型
type Named = { name: string };
type Aged = { age: number };
type NamedAndAged = Named & Aged;
const user: NamedAndAged = { name: "Charlie", age: 28 };3.4 联合类型与交叉类型
// 联合类型:A 或 B
type Result = Success | Error;
interface Success {
status: "success";
data: string;
}
interface Error {
status: "error";
message: string;
}
function handleResult(result: Result) {
if (result.status === "success") {
console.log(result.data); // TypeScript 知道这里是 Success
} else {
console.log(result.message); // TypeScript 知道这里是 Error
}
}
// 交叉类型:A 且 B
type Admin = User & { permissions: string[] };
interface User {
name: string;
email: string;
}
const admin: Admin = {
name: "Diana",
email: "diana@example.com",
permissions: ["read", "write", "delete"],
};3.5 函数类型
// 参数类型和返回值类型
function add(a: number, b: number): number {
return a + b;
}
// 可选参数和默认值
function greet(name: string, greeting?: string): string {
return `${greeting || "Hello"}, ${name}`;
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((acc, n) => acc + n, 0);
}
// 函数重载
function formatDate(value: string): string;
function formatDate(value: number): string;
function formatDate(value: string | number): string {
if (typeof value === "number") {
return new Date(value).toLocaleDateString();
}
return new Date(value).toLocaleDateString();
}3.6 枚举(Enums)
// 数字枚举
enum Direction {
Up = 0,
Down = 1,
Left = 2,
Right = 3,
}
// 字符串枚举
enum HttpStatusCode {
OK = "200",
NotFound = "404",
InternalError = "500",
}
let status: HttpStatusCode = HttpStatusCode.OK;3.7 字面量类型
// 字符串字面量
type Theme = "light" | "dark" | "system";
let theme: Theme = "dark";
// 数字字面量
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 3;
// 布尔字面量
type Truthy = true;3.8 类型断言(Type Assertions)
// as 语法
const canvas = document.getElementById("main") as HTMLCanvasElement;
canvas.getContext("2d");
// 尖括号语法(JSX 文件中不可用)
// const input = <HTMLInputElement>document.getElementById("input");
// 非空断言
const element = document.querySelector(".box")!;
element.classList.add("active");第二层:进阶用法
3.9 泛型(Generics)深入
泛型是 TypeScript 类型系统中最强大的特性之一。它允许你编写可复用的、类型安全的代码,而不仅仅局限于某一种具体类型。
// 泛型函数
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first([1, 2, 3]); // 返回 number | undefined
first(["a", "b"]); // 返回 string | undefined
// 泛型约束(extends)
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(value: T): T {
console.log(value.length);
return value;
}
logLength("hello"); // OK,string 有 length
logLength([1, 2, 3]); // OK,数组有 length
// logLength(123); // 错误,number 没有 length
// 多个泛型参数
function merge<K extends string, V>(key: K, value: V): Record<K, V> {
return { [key]: value } as Record<K, V>;
}
// 泛型默认值
interface PaginatedResponse<T = unknown> {
data: T[];
total: number;
page: number;
}
// 使用默认类型
const res: PaginatedResponse = { data: [], total: 0, page: 1 };3.10 映射类型(Mapped Types)
映射类型可以从一个旧类型生成新类型,通过遍历其属性来创建转换后的类型。
// 基础映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Optional<T> = {
[P in keyof T]?: T[P];
};
interface Todo {
title: string;
description: string;
completed: boolean;
}
type ReadonlyTodo = Readonly<Todo>;
// 等价于 { readonly title: string; readonly description: string; readonly completed: boolean }
// 映射类型 + 重新映射键(TypeScript 4.1+)
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type TodoGetters = Getters<Todo>;
// { getTitle: () => string; getDescription: () => string; getCompleted: () => boolean }3.11 条件类型(Conditional Types)
条件类型让你可以在类型层面做 if-else 判断。
// 基础形式:SomeType extends OtherType ? TrueType : FalseType
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
// infer 关键字:在条件类型中推断类型
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type F1 = Flatten<string[]>; // string
type F2 = Flatten<number>; // number
// 提取函数返回值类型
type GetReturnType<T> = T extends (...args: never[]) => infer R ? R : never;
type R1 = GetReturnType<() => string>; // string
type R2 = GetReturnType<(x: number) => boolean>; // boolean
// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]
// 避免分布式行为:用 [T] 包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDist<string | number>; // (string | number)[]3.12 类型推断(Type Inference)深入
// typeof:获取变量的类型
const config = {
port: 3000,
host: "localhost",
debug: true,
};
type Config = typeof config;
// { port: number; host: string; debug: boolean }
// keyof:获取类型的所有属性名组成的联合类型
type ConfigKeys = keyof Config; // "port" | "host" | "debug"
// 索引访问类型:Type['key']
type PortType = Config["port"]; // number
// ReturnType:获取函数返回值类型
function createuser(name: string) {
return { name, createdAt: new Date() };
}
type User = ReturnType<typeof createUser>;
// { name: string; createdAt: Date }
// Parameters:获取函数参数类型组成的元组
type CreateUserParams = Parameters<typeof createUser>;
// [name: string]3.13 类型守卫(Type Guards)与收窄(Narrowing)
// typeof 守卫
function double(value: string | number): string | number {
if (typeof value === "string") {
return value.repeat(2); // value: string
}
return value * 2; // value: number
}
// instanceof 守卫
function formatDate(value: string | Date): string {
if (value instanceof Date) {
return value.toISOString(); // value: Date
}
return new Date(value).toISOString(); // value: string
}
// in 操作符守卫
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // animal: Fish
} else {
animal.fly(); // animal: Bird
}
}
// 自定义类型守卫(type predicates)
function isString(value: unknown): value is string {
return typeof value === "string";
}
function process(input: unknown) {
if (isString(input)) {
console.log(input.toUpperCase()); // input: string
}
}
// 可辨识联合(Discriminated Unions)
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.x ** 2;
case "triangle":
return (shape.x * shape.y) / 2;
}
}3.14 声明文件(Declaration Files)
声明文件(.d.ts)为已有的 JavaScript 库提供类型信息,让 TypeScript 能够理解它们。
// 编写声明文件 example.d.ts
declare module "my-lib" {
interface MyLibOptions {
timeout?: number;
retries?: number;
}
function init(options: MyLibOptions): void;
function getData(id: string): Promise<string>;
export { init, getData, MyLibOptions };
}
// 使用
import { init, getData } from "my-lib";
init({ timeout: 5000 });
const data = await getData("abc");3.15 模块与命名空间
// ES 模块(推荐方式)
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export const PI = 3.14159;
// app.ts
import { add, PI } from "./math";
// 命名空间(旧式方式,新项目不推荐)
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
}
}
}
const validator = new Validation.EmailValidator();3.16 装饰器(Decorators)
TypeScript 5.0 引入了符合 ECMAScript 标准的装饰器。
// 方法装饰器
function logged(originalMethod: any, context: ClassMethodDecoratorContext) {
const methodName = String(context.name);
function replacementMethod(this: any, ...args: any[]) {
console.log(`LOG: Entering method '${methodName}'.`);
const result = originalMethod.call(this, ...args);
console.log(`LOG: Exiting method '${methodName}'.`);
return result;
}
return replacementMethod;
}
class Calculator {
@logged
add(a: number, b: number): number {
return a + b;
}
}第三层:深度解析
3.17 高级类型体操
将前面学到的所有工具组合起来,可以实现非常强大的类型级编程。
// 深层 Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// 深层 Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 提取 Promise 内部的类型
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;
type Result = UnwrapPromise<Promise<Promise<string>>>; // string
// 将对象的键从 snake_case 转为 camelCase
type SnakeToCamel<S extends string> = S extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
: S;
type CamelCase = SnakeToCamel<"user_name_id">; // "userNameId"
// 递归类型:路径取值
type PathValue<T, P extends string> = P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? PathValue<T[Key], Rest>
: never
: P extends keyof T
? T[P]
: never;
interface Data {
user: {
profile: {
name: string;
};
};
}
type Name = PathValue<Data, "user.profile.name">; // string3.18 tsconfig.json 最佳实践
{
"compilerOptions": {
// 语言与环境
"target": "ES2022", // 编译目标
"lib": ["ES2022", "DOM"], // 类型定义库
"module": "ESNext", // 模块系统
"moduleResolution": "bundler", // 模块解析策略
// 严格模式(推荐全部开启)
"strict": true, // 启用所有严格检查
"noUncheckedIndexedAccess": true, // 数组/对象索引返回 T | undefined
"noImplicitOverride": true, // 覆盖方法必须加 override
"exactOptionalPropertyTypes": true, // 精确的可选属性类型
// 输出控制
"declaration": true, // 生成 .d.ts 文件
"declarationMap": true, // 生成声明映射
"sourceMap": true, // 生成 source map
// 互操作性
"esModuleInterop": true, // 允许默认导入 CommonJS 模块
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true, // 允许导入 JSON
"isolatedModules": true, // 确保每个文件可以独立编译
// 其他
"skipLibCheck": true, // 跳过 .d.ts 文件的类型检查
"forceConsistentCasingInFileNames": true,
},
"include": ["src"],
"exclude": ["node_modules", "dist"],
}3.19 类型系统设计哲学
TypeScript 的类型系统遵循几个核心设计原则:
结构化类型(Structural Typing):TypeScript 使用"鸭子类型"——只要两个类型的结构兼容,它们就是兼容的,不需要显式声明继承关系。
interface Point1 {
x: number;
y: number;
}
interface Point2 {
x: number;
y: number;
}
const p1: Point1 = { x: 1, y: 2 };
const p2: Point2 = p1; // OK,结构兼容类型收窄(Narrowing):TypeScript 的控制流分析会根据 typeof、instanceof、in 操作符、truthiness 检查、赋值语句等自动收窄变量类型。
渐进式采用:你可以从 .js 文件逐步迁移到 .ts,使用 allowJs 和 checkJs 选项控制迁移节奏。any 类型虽然不推荐滥用,但提供了逃生舱。
3.20 性能优化
// 避免过深的类型递归(TypeScript 有递归深度限制)
// 不好的做法
type BadDeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? BadDeepPartial<T[K]> : T[K];
};
// 对于特别深的嵌套,考虑使用具体层级
type Partial3<T> = Partial<{
[K in keyof T]: T[K] extends object
? Partial<{
[K2 in keyof T[K]]: T[K][K2] extends object ? Partial<T[K][K2]> : T[K][K2];
}>
: T[K];
}>;
// 使用 interface 代替交叉类型来提升性能
// 交叉类型会创建新的中间类型,interface 只是声明式的
// 不好的做法
type A = { a: string } & { b: number } & { c: boolean };
// 好的做法
interface B {
a: string;
b: number;
c: boolean;
}
// 项目引用(Project References)加速大型项目编译
// tsconfig.json
// {
// "references": [
// { "path": "./packages/core" },
// { "path": "./packages/utils" }
// ]
// }四、要点笔记(康奈尔笔记法)
康奈尔笔记法:将笔记分为线索栏、笔记栏和总结栏,便于复习和检索。
关键概念速查表
| 线索/关键词 | 详细笔记 |
|---|---|
| 基础类型 | string、number、boolean、null、undefined、symbol、bigint,特殊类型 void、never、unknown |
| 接口(interface) | 定义对象的形状,支持可选属性(?)、只读属性(readonly)、扩展(extends)、声明合并 |
| 类型别名(type) | 给类型起名字,支持联合类型、交叉类型、字面量类型的内联定义,不能声明合并 |
| 联合类型(Union) | A | B,表示值可以是 A 或 B 中的任意一种 |
| 交叉类型(Intersection) | A & B,表示值必须同时满足 A 和 B 的所有要求 |
| 泛型(Generics) | <T> 类型参数化,支持约束(extends)、默认值、多参数 |
| 类型守卫(Type Guards) | typeof、instanceof、in、自定义守卫(value is Type),用于收窄类型 |
| 条件类型(Conditional) | T extends U ? X : Y,配合 infer 推断类型,支持分布式行为 |
| 映射类型(Mapped) | [P in keyof T]: T[P],遍历属性生成新类型,支持键重映射 |
| 模板字面量类型 | `prefix${Type}`,在类型层面操作字符串,配合 Uppercase/Lowercase/Capitalize/Uncapitalize |
| 收窄(Narrowing) | 控制流分析自动收窄类型,基于 typeof、instanceof、in、switch、truthiness 等 |
| 声明文件 | .d.ts 文件为 JS 库提供类型信息,支持 declare module、declare global 等 |
核心 API / 工具类型速查
| 类型 / 工具 | 用途 | 示例 |
|---|---|---|
Partial<T> | 将所有属性变为可选 | Partial<{ a: string; b: number }> → { a?: string; b?: number } |
Required<T> | 将所有属性变为必填 | Required<{ a?: string }> → { a: string } |
Readonly<T> | 将所有属性变为只读 | Readonly<{ a: string }> → { readonly a: string } |
Record<K, V> | 构造键为 K、值为 V 的对象类型 | Record<"a" | "b", number> → { a: number; b: number } |
Pick<T, K> | 从 T 中选取部分属性 | Pick<{ a: string; b: number }, "a"> → { a: string } |
Omit<T, K> | 从 T 中排除部分属性 | Omit<{ a: string; b: number }, "b"> → { a: string } |
Exclude<U, M> | 从联合类型中排除成员 | Exclude<"a" | "b" | "c", "a"> → "b" | "c" |
Extract<U, M> | 从联合类型中提取成员 | Extract<"a" | "b" | "c", "a" | "f"> → "a" |
NonNullable<T> | 排除 null 和 undefined | NonNullable<string | null | undefined> → string |
ReturnType<F> | 获取函数返回值类型 | ReturnType<() => string> → string |
Parameters<F> | 获取函数参数类型的元组 | Parameters<(a: string, b: number) => void> → [a: string, b: number] |
Awaited<T> | 递归解包 Promise | Awaited<Promise<Promise<string>>> → string |
NoInfer<T> | 阻止类型推断 | NoInfer<C> 防止 C 被推断为其他值 |
keyof T | 获取类型所有属性名的联合类型 | keyof { a: string; b: number } → "a" | "b" |
typeof x | 获取变量的类型 | typeof config → { port: number; host: string } |
T[K] | 索引访问类型 | { a: string; b: number }["a"] → string |
本节总结
TypeScript 是 JavaScript 的超集,通过静态类型系统在编译时捕获错误,显著提升代码的可靠性和开发体验。其核心是结构化类型系统——类型兼容性基于形状而非名义声明。泛型是类型系统中最强大的工具,它让代码可以在保持类型安全的同时实现复用。条件类型和映射类型提供了在类型层面进行编程的能力,配合 infer、keyof、typeof 等操作符,能够实现极其灵活的类型转换。TypeScript 的设计哲学是渐进式采用——你可以从纯 JavaScript 项目逐步引入类型,不需要一次性全部改写。
五、复习与实践(SQ3R · Recite & Review)
SQ3R 最后两步:复述核心要点,通过实践巩固理解。
核心要点回顾
- TypeScript 是 JavaScript 的超集,添加了可选的静态类型系统和编译时类型检查。
- 基础类型包括
string、number、boolean、null、undefined、symbol、bigint,以及特殊类型void、never、unknown。 - 接口(interface)和类型别名(type)都用于描述对象的形状,interface 支持声明合并,type 更灵活。
- 联合类型(
|)表示"或"的关系,交叉类型(&)表示"且"的关系。 - 泛型(
<T>)让函数和类型可以参数化,通过extends添加约束。 - 类型守卫(typeof、instanceof、in、自定义 type predicate)帮助 TypeScript 收窄类型。
- 条件类型(
T extends U ? X : Y)在类型层面做条件判断,infer关键字用于推断类型。 - 映射类型遍历已有类型的属性来生成新类型,支持键重映射(
as)。 - 工具类型(
Partial、Required、Readonly、Pick、Omit、Record等)是日常开发的高频工具。 - TypeScript 使用结构化类型(鸭子类型),类型兼容性基于形状而非名义声明。
动手练习
-
基础练习:为一个 TODO 应用定义完整的类型系统。包括
Todo接口(id、title、description、completed、createdAt),以及addTodo、toggleTodo、filterTodos函数的类型签名。 -
进阶练习:实现一个类型安全的
EventEmitter。使用泛型和映射类型,确保on、emit、off方法都有完整的类型提示。要求事件名和回调参数类型之间存在关联。 -
高级练习:实现一个类型安全的 API 路由定义。给定一个路由配置对象,使用模板字面量类型和条件类型,自动推导出每个路由的请求参数类型和响应类型。
-
实战项目:将一个现有的 JavaScript 项目迁移到 TypeScript。从
allowJs: true开始,逐步为关键模块添加类型,编写.d.ts声明文件,最终开启strict: true。
常见陷阱
- 过度使用
any:any会关闭类型检查,尽量使用unknown替代,或者用泛型表达更精确的约束。 - 类型断言滥用:
as只是告诉编译器"相信我",不会做运行时检查。优先使用类型守卫来安全地收窄类型。 - 忘记处理
undefined:开启noUncheckedIndexedAccess后,数组索引和对象键访问可能返回undefined,需要显式处理。 - 循环依赖类型:两个类型互相引用时可能导致循环依赖错误。解决方案是使用
import type和延迟引用。 - 运行时与类型不一致:TypeScript 的类型在编译后会被擦除。不要依赖类型信息做运行时判断——使用 type guards 或 schema 验证库(如 Zod)。
延伸阅读
- TypeScript 官方文档
- TypeScript Handbook
- TypeScript Playground——在线演练场,实时查看类型推导结果
- Type Challenges——类型体操练习题集
- TSConfig Reference——所有编译选项的详细文档