一、概览与提问(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 的核心架构可以理解为三层结构:

基础层:类型系统——包含原始类型(stringnumberbooleannullundefinedsymbolbigint)、对象类型(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 的类型系统想象成一个快递分拣中心

  • 原始类型stringnumberboolean)就像标准化的包裹尺寸——小件、中件、大件,每种都有固定的规格。
  • 接口和类型别名就像是定制的包装盒模板——你可以定义盒子需要多少格子、每个格子放什么。
  • 联合类型就像"可以放小件或中件"的弹性格口——两种都能放,但必须属于其中一种。
  • 泛型就像可调节的货架——根据当天要存放的货物类型来调整货架尺寸,保证每种货物都有合适的空间。
  • 类型守卫就像分拣员扫描条形码——扫描之后,就确定了包裹的具体类型,可以放到对应的格口。
  • 条件类型就像自动化的分拣规则——"如果是易碎品,贴上'轻拿轻放'标签;否则贴上'普通件'标签"。

常见误解澄清

  1. "TypeScript 是另一种语言,需要重新学"——不对。TypeScript 是 JavaScript 的超集,你的 JS 知识完全适用。你可以渐进式地引入类型,不需要一次性改写所有代码。

  2. "TypeScript 让代码更臃肿"——类型注解只占很少的代码量,换来的是编译时错误捕获和更好的 IDE 支持。在大型项目中,这种投入产出比非常划算。

  3. "any 类型就是不用 TypeScript"——虽然 any 绕过了类型检查,但合理使用 unknown 或泛型往往更好。any 是逃生舱,不是日常工具。

  4. "interface 和 type 完全一样"——它们大部分场景可互换,但 interface 支持声明合并(declaration merging),type 支持联合类型和交叉类型的内联定义。选择哪一个取决于具体需求。

  5. "TypeScript 只适合大项目"——即使是小项目,类型系统也能帮你捕获拼写错误、遗漏属性等问题。许多开发者发现,一旦习惯 TS 的开发体验,就很难回到纯 JS 了。

三、锥形深入(西蒙学习法)

西蒙学习法:集中精力、目标导向、锥形深入——从核心开始,逐步扩展到周边。

第一层:核心基础

3.1 基础类型

TypeScript 的基础类型与 JavaScript 的原始类型一一对应,并增加了 voidneverunknown 等特殊类型。

// 原始类型
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">; // string

3.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 的控制流分析会根据 typeofinstanceofin 操作符、truthiness 检查、赋值语句等自动收窄变量类型。

渐进式采用:你可以从 .js 文件逐步迁移到 .ts,使用 allowJscheckJs 选项控制迁移节奏。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" }
//   ]
// }

四、要点笔记(康奈尔笔记法)

康奈尔笔记法:将笔记分为线索栏、笔记栏和总结栏,便于复习和检索。

关键概念速查表

线索/关键词详细笔记
基础类型stringnumberbooleannullundefinedsymbolbigint,特殊类型 voidneverunknown
接口(interface)定义对象的形状,支持可选属性(?)、只读属性(readonly)、扩展(extends)、声明合并
类型别名(type)给类型起名字,支持联合类型、交叉类型、字面量类型的内联定义,不能声明合并
联合类型(Union)A | B,表示值可以是 A 或 B 中的任意一种
交叉类型(Intersection)A & B,表示值必须同时满足 A 和 B 的所有要求
泛型(Generics)<T> 类型参数化,支持约束(extends)、默认值、多参数
类型守卫(Type Guards)typeofinstanceofin、自定义守卫(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 moduledeclare 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 和 undefinedNonNullable<string | null | undefined>string
ReturnType<F>获取函数返回值类型ReturnType<() => string>string
Parameters<F>获取函数参数类型的元组Parameters<(a: string, b: number) => void>[a: string, b: number]
Awaited<T>递归解包 PromiseAwaited<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 的超集,通过静态类型系统在编译时捕获错误,显著提升代码的可靠性和开发体验。其核心是结构化类型系统——类型兼容性基于形状而非名义声明。泛型是类型系统中最强大的工具,它让代码可以在保持类型安全的同时实现复用。条件类型和映射类型提供了在类型层面进行编程的能力,配合 inferkeyoftypeof 等操作符,能够实现极其灵活的类型转换。TypeScript 的设计哲学是渐进式采用——你可以从纯 JavaScript 项目逐步引入类型,不需要一次性全部改写。

五、复习与实践(SQ3R · Recite & Review)

SQ3R 最后两步:复述核心要点,通过实践巩固理解。

核心要点回顾

  1. TypeScript 是 JavaScript 的超集,添加了可选的静态类型系统和编译时类型检查。
  2. 基础类型包括 stringnumberbooleannullundefinedsymbolbigint,以及特殊类型 voidneverunknown
  3. 接口(interface)和类型别名(type)都用于描述对象的形状,interface 支持声明合并,type 更灵活。
  4. 联合类型(|)表示"或"的关系,交叉类型(&)表示"且"的关系。
  5. 泛型(<T>)让函数和类型可以参数化,通过 extends 添加约束。
  6. 类型守卫(typeof、instanceof、in、自定义 type predicate)帮助 TypeScript 收窄类型。
  7. 条件类型(T extends U ? X : Y)在类型层面做条件判断,infer 关键字用于推断类型。
  8. 映射类型遍历已有类型的属性来生成新类型,支持键重映射(as)。
  9. 工具类型(PartialRequiredReadonlyPickOmitRecord 等)是日常开发的高频工具。
  10. TypeScript 使用结构化类型(鸭子类型),类型兼容性基于形状而非名义声明。

动手练习

  1. 基础练习:为一个 TODO 应用定义完整的类型系统。包括 Todo 接口(id、title、description、completed、createdAt),以及 addTodotoggleTodofilterTodos 函数的类型签名。

  2. 进阶练习:实现一个类型安全的 EventEmitter。使用泛型和映射类型,确保 onemitoff 方法都有完整的类型提示。要求事件名和回调参数类型之间存在关联。

  3. 高级练习:实现一个类型安全的 API 路由定义。给定一个路由配置对象,使用模板字面量类型和条件类型,自动推导出每个路由的请求参数类型和响应类型。

  4. 实战项目:将一个现有的 JavaScript 项目迁移到 TypeScript。从 allowJs: true 开始,逐步为关键模块添加类型,编写 .d.ts 声明文件,最终开启 strict: true

常见陷阱

  • 过度使用 anyany 会关闭类型检查,尽量使用 unknown 替代,或者用泛型表达更精确的约束。
  • 类型断言滥用as 只是告诉编译器"相信我",不会做运行时检查。优先使用类型守卫来安全地收窄类型。
  • 忘记处理 undefined:开启 noUncheckedIndexedAccess 后,数组索引和对象键访问可能返回 undefined,需要显式处理。
  • 循环依赖类型:两个类型互相引用时可能导致循环依赖错误。解决方案是使用 import type 和延迟引用。
  • 运行时与类型不一致:TypeScript 的类型在编译后会被擦除。不要依赖类型信息做运行时判断——使用 type guards 或 schema 验证库(如 Zod)。

延伸阅读