TypeScript 教程

TypeScript 是 JavaScript 的超集:在 JS 基础上增加了静态类型、接口、泛型等特性,最终编译为纯 JavaScript 运行。它让大型前端项目更可维护、可扩展、更安全,是目前前端开发的主流选择。

为什么需要 TypeScript

痛点TypeScript 方案
类型错误到运行时才暴露编译期静态检查
IDE 无智能提示类型推导 + 自动补全
重构容易漏改类型系统保护
代码难理解类型即文档

一个直观的例子

// JavaScript:错误到运行时才发现
function add(a, b) {
  return a + b;
}
add("1", 2);    // "12"(字符串拼接,静默出错)

// TypeScript:编译期就报错
function add(a: number, b: number): number {
  return a + b;
}
add("1", 2);    // ❌ 编译错误:类型不匹配

TypeScript 的特性

  • 静态类型:变量、参数、返回值都有类型
  • 类型推导:很多类型可自动推断,不用手写
  • 面向对象:类、接口、泛型、装饰器
  • 兼容 JS:任何 JS 代码都是合法 TS
  • 生态强大:Angular、Vue3、React 全面支持

TypeScript 的编译

TypeScript (.ts) → tsc 编译器 → JavaScript (.js) → 浏览器/Node 运行

谁在用 TypeScript

  • Angular、Vue 3、React 生态全面采用
  • VS Code、Deno 本身就用 TS 开发
  • 绝大多数现代前端团队
💡 提示

TypeScript 的核心理念:类型安全 ≠ 限制,而是让工具和团队帮你发现错误,把 bug 消灭在编译期。

TypeScript 简介

本节介绍 TypeScript 的背景:为什么诞生、解决了什么问题、与 JavaScript 的关系。

诞生背景

  • 2012 年由微软开发并开源
  • 作者 Anders Hejlsberg(C# 之父)
  • 源于大型 JS 项目的维护痛点
  • 2019 年起成为前端最流行的语言之一

TypeScript 解决了什么

问题说明
动态类型隐患JS 变量类型随意变化,难排查
缺乏 IDE 支持无类型信息,补全和重构受限
团队协作困难接口不清晰,沟通成本高
重构风险大改函数签名牵一发动全身

TypeScript 的本质

TypeScript = JavaScript + 类型系统 + 新语法。它不改变 JS 的运行方式,只是多了一层"编译期检查"。

TS 代码
  │ tsc 编译(类型检查 + 转译)
  ▼
JS 代码(类型全部擦除)
  │
  ▼
运行(Node/浏览器)

与 JavaScript 的关系

  • TS 是 JS 的超集:所有 JS 都是合法 TS
  • TS 编译产物就是 JS,兼容所有 JS 生态
  • 可以在现有 JS 项目中逐步引入 TS

设计目标

  • 编译期捕获错误(类型检查)
  • 提供优秀的开发工具体验
  • 保持与 JS 完全兼容
  • 支持未来的 JS 特性
💡 提示

理解"TS 是 JS 的超集,编译后还是 JS"——这是理解一切 TS 概念的基础。

TypeScript 安装

安装 TypeScript 编译器并完成基本配置。

全局安装

npm install -g typescript

# 验证
tsc --version

项目本地安装(推荐)

npm init -y
npm install -D typescript

# 本地运行
npx tsc --version

初始化配置

npx tsc --init    # 生成 tsconfig.json

第一个 TS 文件

// hello.ts
function greet(name: string): string {
  return `你好,${name}!`;
}

console.log(greet("TypeScript"));
# 编译
npx tsc hello.ts

# 运行产物
node hello.js
# 你好,TypeScript!

监听模式(开发常用)

npx tsc --watch    # 文件变化自动重新编译

直接运行 TS(Node 22+)

# Node 22.6+ 原生支持 TS
node hello.ts

# 或使用 tsx(推荐开发用)
npm install -D tsx
npx tsx hello.ts

常见问题

问题解决
tsc 命令找不到确认全局安装或使用 npx
npm 下载慢配置淘宝镜像
编译报错查看错误信息,通常是类型不匹配
💡 提示

现代开发常用 tsx 直接跑 TS(免编译),或用 Vite 内置的 esbuild 转译,体验更流畅。

TypeScript VS Code

在 VS Code 中配置 TypeScript 开发环境——VS Code 对 TS 支持是"一等公民"。

1. 内置支持

VS Code 内置 TypeScript 支持:语法高亮、类型检查、智能提示、跳转定义,开箱即用。

2. 推荐扩展

  • TypeScript Vue Plugin:Vue 单文件中的 TS
  • Prettier:格式化(配合 @typescript-eslint)
  • ESLint:代码检查

3. 创建项目

mkdir ts-demo && cd ts-demo
npm init -y
npm install -D typescript
npx tsc --init

4. 配置 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,          // 严格模式(推荐)
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true
  },
  "include": ["src"]
}

5. 编写代码体验

// src/index.ts
interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: "张三" },
  { id: 2, name: "李四" },
];

// 鼠标悬停 user 时有类型提示
users.forEach(user => console.log(user.name));

6. 调试 TypeScript

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug TS",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "tsx",
      "args": ["${workspaceFolder}/src/index.ts"]
    }
  ]
}

7. 常见快捷键

  • F12:跳转到定义
  • Alt + F12:快速查看定义
  • Shift + F12:查找所有引用
  • Cmd/Ctrl + .:快速修复
💡 提示

VS Code 状态栏右下角显示当前 TS 版本;报错会以红色波浪线标出,悬停即可查看详情和修复建议。

TypeScript 特性

系统了解 TypeScript 的核心特性,建立完整认知。

1. 静态类型

let count: number = 42;
let name: string = "TypeScript";
let isDone: boolean = true;
let list: number[] = [1, 2, 3];

2. 类型推导

// 自动推导为 number,无需手写
let age = 25;
// age = "25"  // ❌ 报错

3. 接口(Interface)

interface User {
  id: number;
  name: string;
  email?: string;       // 可选属性
  readonly createdAt: Date;  // 只读
}

function printUser(user: User) {
  console.log(user.name);
}

4. 泛型(Generic)

function identity<T>(value: T): T {
  return value;
}

identity<number>(42);      // T = number
identity("hello");         // 自动推导

5. 类与面向对象

class Animal {
  constructor(public name: string) {}
  speak(): void {
    console.log(`${this.name} 发出声音`);
  }
}

class Dog extends Animal {
  speak(): void {
    console.log(`${this.name} 汪汪叫`);
  }
}

6. 枚举与联合类型

enum Color { Red, Green, Blue }

type Status = "success" | "error" | "pending";  // 联合类型

let status: Status = "success";

7. 装饰器与高级类型

// 装饰器(实验特性)
function log(target: any, key: string) {
  console.log(`调用 ${key}`);
}

// 工具类型
type PartialUser = Partial<User>;     // 全部可选
type ReadonlyUser = Readonly<User>;   // 全部只读
type NameOnly = Pick<User, "name">;   // 挑选字段
💡 提示

TS 特性虽多,但核心就三类:类型系统(类型/接口/泛型)、面向对象(类/装饰器)、编译工具(tsconfig)。

TypeScript 基础语法

掌握 TypeScript 基础语法:变量、类型注解、函数、语句。

变量声明与类型

// 显式类型注解
let name: string = "CodeNest";
let age: number = 25;
let isActive: boolean = true;

// 类型推导(推荐:少写类型)
let score = 95;            // number
let message = "你好";       // string

// 联合类型
let value: string | number = "text";
value = 123;               // 合法

常量与数组

const MAX_SIZE = 100;          // 常量
const names: string[] = ["A", "B"];
const matrix: number[][] = [[1, 2], [3, 4]];

// 元组
const tuple: [string, number] = ["hello", 42];

函数

// 参数类型 + 返回类型
function add(a: number, b: number): number {
  return a + b;
}

// 可选参数与默认值
function greet(name: string, greeting: string = "你好"): string {
  return `${greeting},${name}`;
}

// 剩余参数
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

条件与循环

// if/else
if (age >= 18) {
  console.log("成年");
} else {
  console.log("未成年");
}

// for / for...of / forEach
for (let i = 0; i < 5; i++) { /* ... */ }
for (const n of names) { console.log(n); }
names.forEach(n => console.log(n));

解构

const user = { id: 1, name: "张三" };
const { id, name } = user;          // 对象解构
const [first, second] = names;      // 数组解构
💡 提示

TypeScript 语法 = JavaScript 语法 + 类型注解。会 JS 就能看懂大部分 TS 代码,类型标注是重点。

TypeScript 基本结构

理解 TypeScript 的基本结构:程序由哪些部分组成,如何组织代码。

TS 文件的基本组成

// 1. 导入(import)
import { useState } from "react";
import type { User } from "./types";

// 2. 类型定义
interface Config {
  debug: boolean;
}

// 3. 类型别名 / 枚举
type Status = "on" | "off";
enum Level { Low, Medium, High }

// 4. 函数 / 类 / 变量
export function run(config: Config): void {
  console.log(config.debug);
}

// 5. 导出(export)
export default run;

模块系统

// types.ts
export interface User { id: number; name: string; }
export type ID = number;

// main.ts
import { User, ID } from "./types";

// 默认导出
// export default function() {...}
// import fn from "./module";

程序入口

// main.ts
import { app } from "./app";

function main() {
  app.start();
}

// 入口调用
main();

tsconfig 决定编译范围

{
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

项目目录组织

src/
├── main.ts          # 入口
├── types/           # 类型定义
│   └── index.ts
├── utils/           # 工具函数
├── services/        # 服务层
├── components/      # 组件(前端)
└── models/          # 数据模型
💡 提示

TS 代码组织与 JS 相同(模块 + 导入导出),区别在于可以额外定义类型文件(.d.ts 或 types 目录)。

TypeScript vs JavaScript

对比 TypeScript 与 JavaScript,看清两者差异与取舍。

核心差异

对比项JavaScriptTypeScript
类型系统动态(运行时)静态(编译期)
类型错误运行时才发现编译期报错
IDE 提示有限完善
接口/泛型不支持支持
枚举/装饰器不支持支持
编译步骤无需要 tsc 编译
学习成本低中

同样代码的对比

// JavaScript
function getTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}
const total = getTotal("not an array");  // 运行时崩溃
// TypeScript
interface Item { price: number }

function getTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}
const total = getTotal("not an array");  // 编译期报错 ✅

什么时候用 TS

  • 中大型项目(团队协作)
  • 需要长期维护的项目
  • 多人修改的公共代码
  • 复杂的数据结构

什么时候可以不用 TS

  • 几十行的小脚本
  • 一次性原型
  • 团队无 TS 经验

TS 的代价

  • 多一层编译步骤
  • 类型标注增加代码量
  • 与第三方库类型不匹配时需要适配
💡 提示

业界共识:项目越大,TS 收益越高。新项目默认 TS,老项目可渐进迁移。

TypeScript tsconfig.json

深入理解 tsconfig.json:TypeScript 编译器的配置文件。

生成配置文件

npx tsc --init

基础配置模板

{
  "compilerOptions": {
    // 目标与模块
    "target": "ES2020",              // 编译目标版本
    "module": "ESNext",              // 模块系统
    "lib": ["ES2020", "DOM"],        // 环境库

    // 严格模式(强烈推荐)
    "strict": true,                  // 开启全部严格检查
    "noImplicitAny": true,           // 禁止隐式 any
    "strictNullChecks": true,        // 严格空值检查

    // 输出
    "outDir": "./dist",              // 输出目录
    "rootDir": "./src",              // 源码目录
    "sourceMap": true,               // 生成 source map

    // 模块解析
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "resolveJsonModule": true,

    // 其他
    "declaration": true,             // 生成 .d.ts 声明
    "noUnusedLocals": true,          // 未使用变量报错
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

常用配置说明

选项作用
target编译到哪个 JS 版本(ES2015/ES2020/ESNext)
module模块格式(CommonJS/ESNext)
strict开启严格类型检查(推荐 true)
outDir / rootDir输出/输入目录
paths路径别名
include / exclude编译范围

路径别名

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}
import { format } from "@utils/format";  // 而非 ../../utils/format

常见配置组合

场景关键配置
Node 项目module: CommonJS, target: ES2020
前端项目module: ESNext, lib: DOM, moduleResolution: bundler
库开发declaration: true
💡 提示

strict: true 是 TS 的核心价值所在——宁可初期多改几处类型错误,也别关掉严格模式。

TypeScript 编译选项

掌握 TypeScript 编译选项:命令行参数与配置项速查。

命令行常用选项

tsc                     # 按 tsconfig 编译
tsc hello.ts            # 编译单个文件
tsc --watch             # 监听模式
tsc --noEmit            # 只检查类型,不输出文件
tsc --outDir dist       # 指定输出目录
tsc --strict            # 开启严格模式
tsc --target ES2020     # 指定目标版本
tsc --module CommonJS   # 指定模块系统
tsc --listFiles         # 列出参与编译的文件

编译选项分类

类别选项说明
目标targetJS 版本
模块module模块格式
严格性strict严格模式总开关
严格性noImplicitAny禁止隐式 any
严格性strictNullChecks空值检查
输出outDir输出目录
输出declaration生成 .d.ts
检查noUnusedLocals未使用局部变量
检查noFallthroughCasesInSwitchswitch 穿透检查

strict 展开的选项

{
  "strict": true
  // 等价于同时开启:
  // noImplicitAny / strictNullChecks / strictFunctionTypes
  // strictBindCallApply / strictPropertyInitialization
  // noImplicitThis / useUnknownInCatchVariables
  // alwaysStrict
}

typecheck 脚本(CI 用)

{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "build": "tsc"
  }
}
npm run typecheck    # CI 中检查类型

增量编译

{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.tsbuildinfo"
  }
}
💡 提示

CI 中执行 tsc --noEmit 做类型检查,是最便宜的 bug 防线——比运行时测试快得多。

TypeScript 基础类型

掌握 TypeScript 基础类型:构建一切类型的基石。

原始类型

let str: string = "hello";          // 字符串
let num: number = 42;              // 数字(含浮点)
let bool: boolean = true;          // 布尔
let big: bigint = 123n;            // 大整数
let sym: symbol = Symbol("id");    // 符号
let nothing: undefined = undefined;
let empty: null = null;

特殊类型

let anyValue: any = "任意类型";     // any:关闭类型检查(慎用)
let unknownValue: unknown = 42;    // unknown:安全的 any
let voidValue: void;               // void:无返回值
let neverValue: never;             // never:永不返回(抛错/死循环)

function fail(): never {
  throw new Error("出错了");
}

数组与元组

let list: number[] = [1, 2, 3];
let list2: Array<string> = ["a", "b"];

// 元组:固定长度与类型
let tuple: [string, number] = ["age", 25];
// tuple = [25, "age"]  // ❌ 类型顺序错误

对象类型

// 内联对象类型
let user: { name: string; age: number } = { name: "张三", age: 25 };

// 接口(推荐)
interface User {
  name: string;
  age: number;
  email?: string;        // 可选
}

// 类型别名
type Point = { x: number; y: number };

枚举

enum Direction {
  Up,        // 0
  Down,      // 1
  Left = 10, // 显式赋值
  Right,     // 11
}

let dir: Direction = Direction.Up;

联合类型与字面量类型

// 联合类型
type ID = string | number;
let id: ID = 42;         // 合法
id = "abc";              // 合法

// 字面量类型
type Status = "active" | "inactive";
let status: Status = "active";
💡 提示

记忆口诀:原始类型小写(string/number/boolean),对象类型用接口,组合类型用 type 别名。

TypeScript 变量声明

掌握 TypeScript 变量声明:var/let/const 与类型声明的正确姿势。

let / const / var

// var:函数作用域,有提升,不推荐
var old = 1;

// let:块级作用域,可重新赋值
let count = 0;
count = 1;

// const:块级作用域,不可重新赋值(推荐默认使用)
const MAX = 100;
// MAX = 200  // ❌ 报错

类型注解与推导

// 显式注解
let name: string = "TypeScript";

// 推导(推荐)
let version = 5.4;          // number
const title = "教程";        // "教程"(字面量类型)

// 推断为联合类型
let status: "on" | "off" = "on";

解构赋值

const user = { id: 1, name: "张三", age: 25 };

// 对象解构 + 类型
const { id, name }: { id: number; name: string } = user;

// 数组解构
const [first, ...rest]: [number, ...number[]] = [1, 2, 3];

// 重命名
const { name: username } = user;

变量类型守卫

let value: string | number = "hello";

if (typeof value === "string") {
  // 此处 value 确定为 string
  console.log(value.toUpperCase());
} else {
  // 此处 value 确定为 number
  console.log(value.toFixed(2));
}

const 断言

// as const:锁定为字面量类型
const config = { debug: true } as const;
// config.debug 类型为 true(字面量),不可修改

const METHODS = ["GET", "POST"] as const;
// 类型为 readonly ["GET", "POST"]
💡 提示

默认用 const,需要重新赋值才用 let;尽量依赖类型推导,少写冗余注解。

TypeScript 运算符

掌握 TypeScript 运算符:算术、比较、逻辑、位运算与类型运算。

算术运算符

const a: number = 10;
const b: number = 3;

a + b   // 13  加
a - b   // 7   减
a * b   // 30  乘
a / b   // 3.333 除
a % b   // 1   取余
a ** b  // 1000 幂
a++     // 自增
--a     // 自减

比较运算符

// 严格比较(推荐)
a === b   // 全等(类型+值)
a !== b   // 不全等

// 宽松比较(不推荐)
a == b    // 有类型转换
a != b

逻辑运算符

// 短路逻辑
const result = condition && doSomething();  // 条件真才执行
const fallback = value || "default";        // 取第一个真值
const maybe = value ?? "fallback";          // 空值合并(null/undefined)

// 逻辑非
const not = !condition;

三元运算符

const status = age >= 18 ? "成年" : "未成年";

可选链与空值合并

interface User { profile?: { name?: string } }

const user: User = {};

// 可选链:避免层层判空
const name = user.profile?.name ?? "匿名";
// 等价于:
// const name = user.profile && user.profile.name ? user.profile.name : "匿名";

位运算符(较少用)

a & b    // 按位与
a | b    // 按位或
a ^ b    // 异或
~a       // 取反
a << 1   // 左移(乘2)
a >> 1   // 右移(除2)

类型运算符

// typeof:类型查询
let x = "hello";
type X = typeof x;        // string

// keyof:键名联合
interface User { id: number; name: string }
type UserKeys = keyof User;    // "id" | "name"

// 类型断言
const value = someUnknown as string;
💡 提示

TS 新增的 ??(空值合并)和 ?.`(可选链) 是最常用的两个现代运算符,能大幅简化判空代码。

TypeScript 条件语句

掌握 TypeScript 条件语句:if/else、switch、三元表达式与类型窄化。

if / else

const score: number = 85;

if (score >= 90) {
  console.log("优秀");
} else if (score >= 60) {
  console.log("及格");
} else {
  console.log("不及格");
}

switch

type Color = "red" | "green" | "blue";

function getColorCode(color: Color): string {
  switch (color) {
    case "red":
      return "#FF0000";
    case "green":
      return "#00FF00";
    case "blue":
      return "#0000FF";
    default:
      // never:所有情况都处理完,不会走到这里
      const exhaustive: never = color;
      return exhaustive;
  }
}

三元表达式

const isAdult = age >= 18 ? true : false;
const label = role === "admin" ? "管理员" : "普通用户";

类型窄化(Type Narrowing)

function process(value: string | number | boolean) {
  // typeof 窄化
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  if (typeof value === "number") {
    return value.toFixed(2);
  }
  return value ? "真" : "假";
}

// in 窄化(对象)
interface Cat { meow(): void }
interface Dog { bark(): void }

function speak(animal: Cat | Dog) {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}

instanceof 窄化

function format(input: Date | string) {
  if (input instanceof Date) {
    return input.toISOString();
  }
  return input.toUpperCase();
}

自定义类型守卫

interface Fish { swim(): void }
interface Bird { fly(): void }

// 类型谓词:is Fish
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();     // 窄化为 Fish
  } else {
    pet.fly();
  }
}
💡 提示

类型窄化是 TS 的强项:条件判断后,TS 自动收窄类型,让代码既安全又清晰。

TypeScript 循环

掌握 TypeScript 循环:for、while、for...of、数组方法。

for 循环

for (let i = 0; i < 5; i++) {
  console.log(i);    // 0 1 2 3 4
}

// 反向
for (let i = 5; i > 0; i--) {
  console.log(i);
}

while / do-while

let count = 0;
while (count < 3) {
  console.log(count);
  count++;
}

let n = 0;
do {
  console.log("至少执行一次", n);
  n++;
} while (n < 2);

for...of(推荐)

const fruits: string[] = ["苹果", "香蕉", "橙子"];

for (const fruit of fruits) {
  console.log(fruit);
}

// 带索引
for (const [index, fruit] of fruits.entries()) {
  console.log(index, fruit);
}

for...in(对象键)

const user = { id: 1, name: "张三" };

for (const key in user) {
  console.log(key, user[key as keyof typeof user]);
}

数组方法(最常用)

const numbers: number[] = [1, 2, 3, 4, 5];

numbers.forEach(n => console.log(n));          // 遍历
const doubled = numbers.map(n => n * 2);       // 映射 [2,4,6,8,10]
const evens = numbers.filter(n => n % 2 === 0); // 过滤 [2,4]
const sum = numbers.reduce((acc, n) => acc + n, 0);  // 聚合 15
const found = numbers.find(n => n > 3);        // 查找 4
const hasLarge = numbers.some(n => n > 4);     // 存在
const allPositive = numbers.every(n => n > 0); // 全部

break / continue

for (const n of numbers) {
  if (n === 2) continue;      // 跳过本次
  if (n === 4) break;         // 终止循环
  console.log(n);             // 1 3
}
💡 提示

遍历数组优先 for...of 和数组方法(map/filter/reduce),代码更简洁且避免索引错误。

TypeScript 函数

掌握 TypeScript 函数:类型注解、可选参数、重载、泛型函数。

函数类型注解

// 参数 + 返回值类型
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const multiply = (a: number, b: number): number => a * b;

// 函数类型变量
let operation: (a: number, b: number) => number;
operation = add;

可选与默认参数

// 可选参数(必须放最后)
function greet(name: string, greeting?: string): string {
  return `${greeting ?? "你好"},${name}`;
}

// 默认参数
function createUser(name: string, age: number = 18): object {
  return { name, age };
}

剩余参数

function sumAll(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

sumAll(1, 2, 3, 4);   // 10

泛型函数

// 泛型:T 为类型参数,调用时确定
function identity<T>(value: T): T {
  return value;
}

identity<number>(42);
identity("hello");    // 自动推导

// 多个泛型参数
function pair<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

泛型约束

interface HasLength {
  length: number;
}

// T 必须具有 length 属性
function getLength<T extends HasLength>(value: T): number {
  return value.length;
}

getLength("hello");       // 5
getLength([1, 2, 3]);     // 3
// getLength(42)          // ❌ number 没有 length

函数返回 void 与 never

// void:无返回值
function log(message: string): void {
  console.log(message);
}

// never:永不返回
function throwError(message: string): never {
  throw new Error(message);
}
💡 提示

给函数写清晰的参数与返回类型,等于免费获得文档和编译期校验——这是 TS 最直接的收益。

TypeScript 函数重载

掌握 TypeScript 函数重载:同一函数名,不同参数类型/数量的多个签名。

为什么需要重载

有时函数根据参数类型返回不同类型,用联合类型表达不清,用重载可以精确描述每种情况。

基础重载

// 重载签名(多个)
function format(input: string): string;
function format(input: number): string;
function format(input: boolean): string;

// 实现签名(只有一份)
function format(input: string | number | boolean): string {
  return String(input);
}

format("hello");   // "hello"
format(42);        // "42"

返回不同结构

interface User { id: number; name: string }
interface Admin extends User { role: "admin" }

// 根据参数返回不同类型
function findUser(id: number): User;
function findUser(id: number, includeAdmin: true): Admin;
function findUser(id: number, includeAdmin?: boolean): User | Admin {
  const base: User = { id, name: "张三" };
  return includeAdmin ? { ...base, role: "admin" } : base;
}

const user = findUser(1);            // User
const admin = findUser(1, true);     // Admin

参数数量不同的重载

// 1 个参数:默认 10 条
function fetchItems(limit?: number): Item[];
// 2 个参数:分页
function fetchItems(page: number, pageSize: number): Item[];

function fetchItems(a: number, b?: number): Item[] {
  if (b === undefined) {
    return getFromServer({ limit: a });
  }
  return getFromServer({ page: a, pageSize: b });
}

类方法重载

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: any, b: any): any {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(1, 2);       // number
calc.add("a", "b");   // string

重载的规则

  • 重载签名可以多个,实现签名只有一个
  • 实现签名需兼容所有重载
  • 调用时按重载签名检查类型
  • 重载实现要处理所有情况(用 any/联合类型)
💡 提示

重载让 API 调用方的类型提示更精确;但滥用会增加复杂度——能用联合类型/泛型解决时优先用简单方案。

TypeScript 箭头函数

掌握 TypeScript 箭头函数:语法、this 绑定与类型标注。

基础语法

// 普通函数
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const add = (a: number, b: number): number => a + b;

// 多语句需要花括号
const add = (a: number, b: number): number => {
  const result = a + b;
  return result;
};

简化写法

// 单参数可省略括号
const double = (n: number) => n * 2;
const double2 = n => n * 2;     // 类型依赖推导

// 返回对象需要括号
const createUser = (name: string) => ({ name, age: 18 });

// 无参数
const now = () => Date.now();

this 绑定

class Counter {
  count = 0;

  // 箭头函数:this 绑定到定义时的对象
  increment = () => {
    this.count++;
  };

  // 普通方法:this 取决于调用方式
  normalIncrement() {
    this.count++;
  }
}

const c = new Counter();
const fn = c.increment;      // 箭头函数:this 仍指向 c ✅
const fn2 = c.normalIncrement;  // 普通方法:this 丢失 ❌

回调中的箭头函数

const numbers = [1, 2, 3];

// 简洁回调
const doubled = numbers.map(n => n * 2);

// 带类型
const evens: number[] = numbers.filter((n: number) => n % 2 === 0);

// 异步
const fetchData = async (): Promise<Data> => {
  const res = await fetch("/api/data");
  return res.json();
};

函数类型(作为参数)

// 回调类型
function process(list: number[], callback: (n: number) => void): void {
  list.forEach(callback);
}

process([1, 2, 3], n => console.log(n * 10));
💡 提示

箭头函数不绑定自己的 this(继承外层),且不能用作构造函数——这两点是它与普通函数最大的区别。

TypeScript 迭代器

掌握 TypeScript 迭代器与可迭代对象:for...of 的工作原理与自定义迭代器。

可迭代协议

可迭代对象实现 [Symbol.iterator] 方法,返回迭代器(next() 返回 { value, done })。

// 内置可迭代对象
const arr = [1, 2, 3];
const str = "hello";
const map = new Map([["a", 1]]);
const set = new Set([1, 2, 3]);

for (const item of arr) console.log(item);
for (const ch of str) console.log(ch);
for (const [k, v] of map) console.log(k, v);

手动迭代

const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next());  // { value: 10, done: false }
console.log(iterator.next());  // { value: 20, done: false }
console.log(iterator.next());  // { value: 30, done: false }
console.log(iterator.next());  // { value: undefined, done: true }

自定义可迭代对象

class Range {
  constructor(private start: number, private end: number) {}

  // 实现迭代协议
  [Symbol.iterator](): Iterator<number> {
    let current = this.start;
    const end = this.end;

    return {
      next(): IteratorResult<number> {
        if (current <= end) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  }
}

for (const n of new Range(1, 5)) {
  console.log(n);   // 1 2 3 4 5
}

展开运算符(依赖迭代)

const arr = [1, 2, 3];
const copy = [...arr];               // 数组复制
const chars = [..."abc"];            // ['a','b','c']
const merged = [...arr, 4, 5];       // 合并
const obj = { ...{ a: 1 }, b: 2 };   // 对象展开

生成器(Generator)

// 生成器是特殊的迭代器
function* countUp(limit: number): Generator<number> {
  for (let i = 1; i <= limit; i++) {
    yield i;          // 产出值,暂停
  }
}

for (const n of countUp(3)) {
  console.log(n);     // 1 2 3
}

// 惰性求值:用到才计算
function* infinite() {
  let i = 0;
  while (true) yield i++;
}
const gen = infinite();
console.log(gen.next().value);  // 0
console.log(gen.next().value);  // 1
💡 提示

迭代协议让自定义类型也能被 for...of、展开运算符使用——实现 [Symbol.iterator] 即可。

TypeScript async/await

掌握 TypeScript 中的 async/await:异步编程的现代写法。

async 函数

// async 函数总是返回 Promise
async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// 调用
fetchUser(1).then(user => console.log(user));
// 或
const user = await fetchUser(1);

async/await 的返回类型

// 返回值会被包装为 Promise
async function getNumber(): Promise<number> {
  return 42;             // 自动包成 Promise.resolve(42)
}

// 无返回值 → Promise<void>
async function log(msg: string): Promise<void> {
  console.log(msg);
}

错误处理

async function loadData(): Promise<Data> {
  try {
    const res = await fetch("/api/data");
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (error) {
    console.error("加载失败:", error);
    throw error;          // 重新抛出,让调用方处理
  }
}

// 调用方
try {
  const data = await loadData();
} catch (e) {
  showError("数据加载失败");
}

并发执行

// Promise.all:并发(推荐)
const [user, posts] = await Promise.all([
  fetchUser(1),
  fetchPosts(1),
]);

// Promise.allSettled:全部执行完(含失败)
const results = await Promise.allSettled([
  fetch("a"), fetch("b"),
]);
// results: [{status:"fulfilled",value}, {status:"rejected",reason}]

// Promise.race:最快完成
const first = await Promise.race([fetchA(), fetchB()]);

串行 vs 并行

// 串行:逐个执行(慢)
for (const id of ids) {
  await fetchUser(id);
}

// 并行:同时执行(快)
await Promise.all(ids.map(id => fetchUser(id)));

超时控制

function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  return Promise.race([
    promise,
    new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error("超时")), ms)
    ),
  ]);
}

const data = await withTimeout(fetchData(), 5000);
💡 提示

多个独立请求用 Promise.all 并发;循环内避免逐个 await,改为 map + Promise.all。

TypeScript Promise

掌握 TypeScript 中的 Promise:类型定义与常见模式。

创建 Promise

// 带类型的 Promise
function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function fetchUser(id: number): Promise<User> {
  return new Promise((resolve, reject) => {
    if (id <= 0) {
      reject(new Error("无效 ID"));
      return;
    }
    setTimeout(() => resolve({ id, name: "张三" }), 100);
  });
}

Promise 类型

// 泛型 Promise<T>
const p1: Promise<string> = Promise.resolve("hello");
const p2: Promise<number> = Promise.resolve(42);

// Promise.all 返回数组
const ps: Promise<[number, string]> = Promise.all([
  Promise.resolve(1),
  Promise.resolve("a"),
]);

// Promise 链
fetchUser(1)
  .then(user => {
    console.log(user);
    return user.id;              // 传递值
  })
  .then(id => fetchPosts(id))    // 链式
  .catch(err => console.error(err))
  .finally(() => console.log("完成"));

Promise 静态方法

Promise.resolve(value)        // 立即成功
Promise.reject(error)         // 立即失败
Promise.all([p1, p2])         // 全部成功才成功
Promise.allSettled([p1, p2])  // 全部完成后返回结果
Promise.race([p1, p2])        // 最快完成
Promise.any([p1, p2])         // 第一个成功

async 与 Promise 的关系

// async/await 是 Promise 的语法糖
async function getData() {
  return await fetchData();
}

// 等价于
function getData() {
  return fetchData().then(data => data);
}

类型守卫处理 Promise 结果

interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}

async function get<T>(url: string): Promise<ApiResponse<T>> {
  const res = await fetch(url);
  return res.json();
}

// 使用泛型接口
const resp = await get<User[]>("/api/users");
if (resp.code === 0) {
  console.log(resp.data);   // User[]
}
💡 提示

给 Promise 和 async 函数标注返回类型(Promise<T>),类型系统会在 await 后自动推导出 T。

TypeScript Number

掌握 TypeScript 中 Number 的使用:数值类型、方法、边界。

Number 类型

let count: number = 42;
let pi: number = 3.14159;
let negative: number = -10;
let hex: number = 0xff;        // 255(十六进制)
let binary: number = 0b1010;   // 10(二进制)
let octal: number = 0o17;      // 15(八进制)

// 科学计数
let big = 1.5e8;   // 150000000

Number 方法

const n = 123.456;

n.toFixed(2);        // "123.46"(保留两位)
n.toString(16);      // "7b"(转字符串)
n.toExponential(2);  // "1.23e+2"
Number.parseInt("42");   // 42(解析整数)
Number.parseFloat("3.14"); // 3.14
Number.isInteger(42);  // true
Number.isNaN(NaN);     // true

特殊值

Number.MAX_VALUE      // 最大数
Number.MIN_VALUE      // 最小正数
Number.MAX_SAFE_INTEGER  // 9007199254740991
Number.POSITIVE_INFINITY  // Infinity
Number.NEGATIVE_INFINITY
NaN                    // 非数字

// 判断
Number.isFinite(42);       // true
Number.isFinite(Infinity); // false
Number.isNaN(0 / 0);       // true

类型安全操作

// 字符串转数字
const price: number = Number("299");       // 299
const val = Number("abc");                 // NaN(不要直接转)

// 安全解析
function toNumber(input: string): number | null {
  const n = Number(input);
  return Number.isNaN(n) ? null : n;
}

// 取整
Math.floor(3.9);   // 3
Math.ceil(3.1);    // 4
Math.round(3.5);   // 4
Math.trunc(3.9);   // 3

运算注意

0.1 + 0.2;      // 0.30000000000000004(浮点精度问题)
// 处理:转整数或 toFixed
Math.round((0.1 + 0.2) * 100) / 100;   // 0.3

// 金额处理用整数分
const cents = Math.round(19.99 * 100);  // 1999
💡 提示

浮点精度问题:涉及金额时用分(整数)存储和计算,避免 0.1+0.2 的坑。

TypeScript String

掌握 TypeScript 中 String 的使用:类型、方法与模板字符串。

String 类型

let greeting: string = "hello";
let quote: string = 'world';
let template: string = `多行
字符串`;

// 模板字符串插值
const name = "CodeNest";
const message = `欢迎来到 ${name}!`;
console.log(message);   // 欢迎来到 CodeNest!

常用方法

const str = "Hello, TypeScript";

str.length;                 // 17
str.toUpperCase();          // "HELLO, TYPESCRIPT"
str.toLowerCase();          // "hello, typescript"
str.includes("Type");       // true
str.startsWith("Hello");    // true
str.endsWith("Script");     // true

str.indexOf("l");           // 2
str.slice(0, 5);            // "Hello"
str.substring(7);           // "TypeScript"
str.split(", ");            // ["Hello", "TypeScript"]
str.replace("Hello", "Hi"); // "Hi, TypeScript"

str.trim();                 // 去首尾空白
str.padStart(10, "*");      // "***Hello, TypeScript"? 不,长度不够时
"5".padStart(3, "0");       // "005"

正则配合

const text = "联系 13812345678 或 13987654321";

// 提取所有手机号
const phones = text.match(/1[3-9]\d{9}/g);
// ["13812345678", "13987654321"]

// 校验
const isValidEmail = (email: string): boolean =>
  /^[\w.-]+@[\w-]+\.[\w.]+$/.test(email);

类型安全字符串操作

// 模板字符串类型(TS 4.1+)
type EventName = `on${"Click" | "Hover" | "Focus"}`;
// 等价于 "onClick" | "onHover" | "onFocus"

// 字符串字面量工具类型
type UppercaseName = Uppercase<"hello">;  // "HELLO"
type LowerName = Lowercase<"ABC">;        // "abc"
type CapName = Capitalize<"hello">;       // "Hello"

null 安全检查

function getInitial(name: string | null): string {
  // 可选链 + 空值合并
  return name?.charAt(0).toUpperCase() ?? "?";
}

getInitial("alice");   // "A"
getInitial(null);      // "?"
💡 提示

模板字符串(反引号)是日常最常用的字符串写法,配合插值表达式让拼接更清晰。

TypeScript Array(数组)

掌握 TypeScript 数组:类型定义、常用方法与类型安全。

数组类型

// 两种写法
let list1: number[] = [1, 2, 3];
let list2: Array<string> = ["a", "b"];

// 对象数组
interface User { id: number; name: string }
let users: User[] = [{ id: 1, name: "张三" }];

// 只读数组
const readonlyList: readonly number[] = [1, 2, 3];
// readonlyList.push(4)  // ❌ 不可修改

// 多维数组
let matrix: number[][] = [[1, 2], [3, 4]];

常用方法(类型安全)

const numbers: number[] = [1, 2, 3, 4, 5];

// 增删
numbers.push(6);              // 尾部添加
numbers.pop();                // 尾部删除
numbers.unshift(0);           // 头部添加
numbers.shift();              // 头部删除

// 查询
numbers.indexOf(3);           // 2
numbers.includes(5);          // true
numbers.find(n => n > 3);     // 4
numbers.findIndex(n => n > 3);// 3

// 变换(返回新数组)
numbers.map(n => n * 2);      // [2,4,6,8,10]
numbers.filter(n => n % 2 === 0);  // [2,4]
numbers.slice(1, 3);          // [2,3]
numbers.concat([6]);          // [1..6]
numbers.sort((a, b) => a - b); // 排序
numbers.reverse();            // 反转

reduce 聚合

// 求和
const sum = numbers.reduce((acc, n) => acc + n, 0);

// 分组
interface Item { cat: string; price: number }
const items: Item[] = [
  { cat: "a", price: 10 },
  { cat: "b", price: 20 },
  { cat: "a", price: 30 },
];

const byCat = items.reduce<Record<string, number>>((acc, item) => {
  acc[item.cat] = (acc[item.cat] ?? 0) + item.price;
  return acc;
}, {});
// { a: 40, b: 20 }

解构与展开

const [first, second, ...rest] = numbers;
// first=1, second=2, rest=[3,4,5]

const copy = [...numbers];        // 浅拷贝
const merged = [...numbers, 6, 7];

数组类型守卫

// 过滤 undefined
const list: (number | undefined)[] = [1, undefined, 3];
const clean: number[] = list.filter((n): n is number => n !== undefined);
// [1, 3]
💡 提示

用 readonly 数组声明"只读意图",配合 filter 类型谓词(n is number)实现类型安全的数据清洗。

TypeScript Map 对象

掌握 TypeScript Map 对象:键值对的类型化集合。

Map 类型

// 类型化的 Map:键 string,值 number
const scores = new Map<string, number>();

scores.set("张三", 90);
scores.set("李四", 85);
scores.get("张三");      // 90
scores.has("张三");      // true
scores.delete("李四");   // true
scores.size;             // 1
scores.clear();          // 清空

初始化 Map

// 从数组初始化
const map = new Map<string, number>([
  ["a", 1],
  ["b", 2],
]);

// 从对象转换
const obj = { x: 1, y: 2 };
const map2 = new Map(Object.entries(obj));
// Map {"x" => 1, "y" => 2}

遍历 Map

const map = new Map<string, number>([["a", 1], ["b", 2]]);

// 键值对
for (const [key, value] of map) {
  console.log(key, value);
}

// forEach
map.forEach((value, key) => console.log(key, value));

// 只有键 / 只有值
[...map.keys()];     // ["a", "b"]
[...map.values()];   // [1, 2]

Map vs 对象

对比项MapObject
键类型任意(含对象/函数)字符串/Symbol
顺序插入顺序不保证
sizemap.size需 Object.keys()
性能频繁增删更快一般
JSON 序列化需转换原生支持

实战:计数

function countWords(words: string[]): Map<string, number> {
  const counter = new Map<string, number>();
  for (const w of words) {
    counter.set(w, (counter.get(w) ?? 0) + 1);
  }
  return counter;
}

const result = countWords(["a", "b", "a", "c", "a"]);
// Map {"a" => 3, "b" => 1, "c" => 1}
💡 提示

需要"任意类型键"或"频繁增删"时用 Map;需要序列化 JSON 或简单结构时用对象。

TypeScript 枚举

掌握 TypeScript 枚举(Enum):命名常量集合。

数字枚举

enum Direction {
  Up,        // 0(默认从 0 开始)
  Down,      // 1
  Left,      // 2
  Right,     // 3
}

let dir: Direction = Direction.Up;   // 0
Direction[0];                        // "Up"(反向映射)

自定义值

enum StatusCode {
  OK = 200,
  NotFound = 404,
  ServerError = 500,
}

enum UserRole {
  Admin = "admin",
  User = "user",
  Guest = "guest",
}

const role: UserRole = UserRole.Admin;  // "admin"

常量枚举(const enum)

// const enum 编译后不保留对象,性能更好
const enum Color {
  Red = "red",
  Green = "green",
}

const color: Color = Color.Red;  // 编译后直接是 "red"

枚举遍历

enum Fruit { Apple, Banana, Orange }

// 数字枚举可反向映射
Object.keys(Fruit).filter(k => isNaN(Number(k)));
// ["Apple", "Banana", "Orange"]

// 字符串枚举
Object.values(UserRole);   // ["admin", "user", "guest"]

枚举 vs 联合类型

对比项EnumUnion(联合)
运行时存在✅ 有对象❌ 纯编译期
反向映射✅❌
类型安全严格严格
推荐度传统选择现代推荐(更简单)

现代替代:const 对象 + 联合

// 推荐的做法
const Status = {
  Active: "active",
  Inactive: "inactive",
} as const;

type Status = typeof Status[keyof typeof Status];
// 等价于 "active" | "inactive"

const s: Status = Status.Active;
Object.values(Status);   // ["active", "inactive"]
💡 提示

新代码优先用 as const 对象 + 联合类型(更简单、无运行时开销);枚举适合需要反向映射或传统代码风格。

TypeScript 类型推断

掌握 TypeScript 类型推断:TS 如何自动推导类型,以及如何善用它。

基础推断

// 自动推断为基础类型
let name = "TypeScript";        // string
let age = 25;                   // number
let isDone = false;             // boolean

// 推断为联合类型
let status = "loading" as "loading" | "done";
let value = [1, "two"];         // (string | number)[]

// 函数返回值推断
function add(a: number, b: number) {
  return a + b;                 // 推断为 number
}

上下文类型推断

// 事件回调自动推断
const button = document.querySelector("button");
button?.addEventListener("click", (event) => {
  // event 自动推断为 MouseEvent
  console.log(event.clientX);
});

// 数组方法推断
const users = [{ id: 1, name: "张三" }, { id: 2, name: "李四" }];
users.forEach(user => {
  // user 自动推断为 { id: number; name: string }
  console.log(user.name);
});

最佳通用类型

// 推断出所有元素类型的联合
const arr = [1, "two", true];
// (string | number | boolean)[]

// 无最佳类型时用联合
let mixed: (string | number)[] = [];
mixed.push(1);
mixed.push("a");

显式注解 vs 推断

// 需要显式注解的场景:
// 1. 函数参数必须注解
function greet(name: string): string { return `Hi ${name}`; }

// 2. 返回复杂类型时
function getConfig(): { debug: boolean; env: string } {
  return { debug: true, env: "prod" };
}

// 3. 空数组需要注解(否则是 any[])
const list: number[] = [];

类型收窄与推断配合

function process(data: string | null) {
  // 收窄后推断
  if (data !== null) {
    // 此处 data 推断为 string
    return data.toUpperCase();
  }
  return "空值";
}

推断的局限

// 对象属性推断较宽
const obj = { count: 0 };
obj.count = 1;        // 可赋值(推断为 number)
// obj.count = "str"  // ❌ 报错

// 需要精确字面量类型时用 as const
const precise = { count: 0 } as const;
// precise.count 类型是 0(字面量)
💡 提示

TypeScript 的类型推断已足够智能:能推断就不写注解,代码更简洁;只有参数、导出 API、复杂结构才显式标注。

TypeScript 类型断言

掌握 TypeScript 类型断言:告诉编译器"我知道这个值的类型"。

基础断言

// 方式一:as 语法(推荐)
const value: unknown = "hello";
const str = value as string;

// 方式二:尖括号语法(JSX 中不可用)
const str2 = <string>value;

console.log(str.toUpperCase());   // HELLO

DOM 元素断言

// 常见场景:获取 DOM 元素
const input = document.getElementById("search") as HTMLInputElement;
input.value = "hello";

// 或者更安全的方式
const maybeInput = document.getElementById("search");
if (maybeInput instanceof HTMLInputElement) {
  maybeInput.value = "hello";   // 类型守卫,无需断言
}

接口兼容的断言

interface User { id: number; name: string }

// unknown/any 可以断言为任意类型
const data: unknown = JSON.parse('{"id":1,"name":"张三"}');
const user = data as User;
console.log(user.name);   // 张三

双重断言(慎用)

// 不相关的类型直接断言会报错
// const n = "abc" as number;   // ❌

// 双重断言绕过(危险,尽量避免)
const n = "abc" as unknown as number;

断言 vs 类型守卫

// 断言:编译期,无运行时检查
const user = data as User;   // 运行时 data 可能不是 User!

// 类型守卫:运行时真正检查
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" && value !== null &&
    "id" in value && "name" in value
  );
}

if (isUser(data)) {
  // 安全访问
  console.log(data.name);
}

const 断言

// as const:锁定字面量
const method = "GET" as const;
// method 类型是 "GET" 而非 string

const tuple = [1, 2] as const;
// 类型为 readonly [1, 2]

// 应用:配置对象
const CONFIG = {
  api: "https://api.example.com",
  timeout: 5000,
} as const;
⚠️ 注意

断言不会做运行时检查——错误断言可能导致运行时崩溃。对不可信数据(如 JSON.parse 结果),用类型守卫更安全。

TypeScript null/undefined

掌握 TypeScript 中 null 与 undefined 的处理:严格空值检查与安全访问。

null vs undefined

// undefined:变量未赋值
let a: number | undefined;
// a === undefined  → true

// null:显式赋空
let b: string | null = null;
// b === null  → true

// 都表示"没有值",但语义不同
const nothing: null = null;
const missing: undefined = undefined;

strictNullChecks(推荐开启)

// tsconfig.json
{
  "compilerOptions": {
    "strictNullChecks": true
  }
}
// 开启后:
let name: string = "hello";
// name = null       // ❌ 报错
// name = undefined  // ❌ 报错

// 需要时可空
let nickname: string | null = null;
nickname = "老王";     // ✅

可选链(?.)

interface User {
  profile?: {
    address?: { city?: string }
  };
}

const user: User = {};

// 层层判空 → 可选链
const city = user.profile?.address?.city;
// undefined(任一环节为空就返回 undefined)

// 等价的老写法
const city2 = user.profile && user.profile.address && user.profile.address.city;

空值合并(??)

// ?? 只在 null/undefined 时取默认
const name = user.profile?.name ?? "匿名";
const port = config.port ?? 8080;

// 与 || 的区别:|| 对所有假值生效
const n = 0;
n || 10;    // 10(0 是假值)
n ?? 10;    // 0(0 不是 null/undefined)

类型守卫处理空值

function format(value: string | null | undefined): string {
  // 收窄:排除 null 和 undefined
  if (value === null || value === undefined) {
    return "无值";
  }
  return value.toUpperCase();   // 此处为 string
}

// 简写
function format2(value: string | null | undefined): string {
  return value?.toUpperCase() ?? "无值";
}

非空断言(!,慎用)

// ! 表示"我确定不为空"
const el = document.getElementById("app")!;
el.innerHTML = "content";

// 有风险:运行时可能为空
// 推荐:先判断再使用
const maybeEl = document.getElementById("app");
if (maybeEl) {
  maybeEl.innerHTML = "content";
}
💡 提示

组合拳:可选链 ?. 安全访问 + 空值合并 ?? 给默认值,是处理空值的最优雅方式。

TypeScript Symbol

掌握 TypeScript Symbol:唯一标识符类型。

Symbol 基础

// 每次创建都是唯一值
const s1 = Symbol("id");
const s2 = Symbol("id");
s1 === s2;    // false(即使描述相同)

// 类型标注
let sym: symbol = Symbol("key");

// 唯一 Symbol
const unique: unique symbol = Symbol("unique");
// unique symbol 类型只能由 Symbol()/Symbol.for() 产生

Symbol 作为对象键

const KEY = Symbol("dataKey");

const obj = {
  [KEY]: "秘密数据",
  normal: "普通数据",
};

// 用 Symbol 访问
console.log(obj[KEY]);    // 秘密数据

// Symbol 键不出现在常规遍历中
Object.keys(obj);         // ["normal"]
JSON.stringify(obj);      // {"normal":"普通数据"}

Symbol.for 全局注册表

// Symbol.for:全局共享(同描述同 Symbol)
const a = Symbol.for("app.version");
const b = Symbol.for("app.version");
a === b;    // true(全局注册表)

// 查看描述
Symbol.keyFor(a);   // "app.version"

内置 Symbol(Well-known Symbols)

// Symbol.iterator:迭代协议
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();

// Symbol.toStringTag:自定义类型标签
class MyClass {
  get [Symbol.toStringTag]() { return "MyClass"; }
}
Object.prototype.toString.call(new MyClass());
// "[object MyClass]"

Symbol 与类型安全

// 用 Symbol 做"品牌"防混淆
declare const brand: unique symbol;

type Branded<T, B extends string> = T & { readonly [brand]: B };

type UserId = Branded<number, "UserId">;
type OrderId = Branded<number, "OrderId">;

function createUserId(id: number): UserId {
  return id as UserId;
}

const uid = createUserId(1);
// const oid: OrderId = uid;  // ❌ 类型不兼容(品牌不同)
💡 提示

Symbol 常用于:对象私有元数据、迭代协议、类型品牌化。日常开发中 Symbol.iterator 和类型品牌最实用。

TypeScript 特殊类型

掌握 TypeScript 特殊类型:any、unknown、never、void 的区别与用法。

any:关闭类型检查(慎用)

// any:任意类型,不做检查
let value: any = "hello";
value = 42;         // ✅ 随意赋值
value.foo.bar;      // ✅ 不报错(运行时可能崩溃)

// 避免显式使用 any
// ❌ function fn(): any {...}
// ✅ 用 unknown 或具体类型

unknown:安全的 any

// unknown:任意类型,但使用前必须收窄
let data: unknown = "hello";
data = 42;          // ✅ 赋值随意

// ❌ 直接使用报错
// data.toUpperCase();

// ✅ 收窄后使用
if (typeof data === "string") {
  data.toUpperCase();   // 此处是 string
}

// 典型场景:JSON.parse 返回 unknown
const parsed: unknown = JSON.parse(rawJson);

void:无返回值

// 函数不返回值
function log(msg: string): void {
  console.log(msg);
}

// 回调类型常用 void(允许返回任意值但不使用)
function forEach(arr: number[], cb: (n: number) => void): void {
  for (const n of arr) cb(n);
}

never:永不发生

// 抛错:永不返回
function fail(): never {
  throw new Error("出错了");
}

// 死循环
function loop(): never {
  while (true) {}
}

// 穷尽检查:确保 switch 覆盖所有情况
type Shape = "circle" | "square";

function area(shape: Shape): number {
  switch (shape) {
    case "circle": return 3.14;
    case "square": return 4;
    default:
      const _exhaustive: never = shape;   // 若漏掉情况,这里报错
      return _exhaustive;
  }
}

四者对比

类型含义使用建议
any任意值,不检查❌ 尽量避免
unknown任意值,需收窄✅ 外部数据首选
void无返回✅ 函数返回值
never永不发生✅ 异常/穷尽检查
💡 提示

规则:unknown 替代 any(外部数据)、never 做穷尽检查、void 标无返回。

TypeScript Set/WeakMap

掌握 TypeScript 中的 Set 与 WeakMap:集合与弱引用映射。

Set 类型

// 类型化 Set
const set = new Set<number>();

set.add(1);
set.add(2);
set.add(1);          // 重复添加被忽略
set.size;            // 2
set.has(2);          // true
set.delete(1);       // true
set.clear();

Set 初始化与遍历

// 初始化
const nums = new Set<number>([1, 2, 3, 3, 2]);
// Set(3) {1, 2, 3} —— 自动去重

// 遍历
for (const n of nums) console.log(n);
nums.forEach(n => console.log(n));
[...nums];           // [1, 2, 3]

Set 实战:去重

function unique<T>(arr: T[]): T[] {
  return [...new Set(arr)];
}

unique([1, 2, 2, 3, 3, 3]);   // [1, 2, 3]
unique(["a", "b", "a"]);      // ["a", "b"]

WeakMap(弱引用)

// WeakMap:键必须是对象,弱引用(不阻止 GC)
const cache = new WeakMap<object, any>();

const obj = {};
cache.set(obj, "数据");

cache.get(obj);      // "数据"
cache.has(obj);      // true
cache.delete(obj);

// 特点:
// - 键必须是对象(不能是原始类型)
// - 不可遍历(无 keys/values/size)
// - 对象被回收时,条目自动消失

WeakMap 实战:对象元数据

// 给 DOM 元素附加数据而不泄漏内存
const elementData = new WeakMap<HTMLElement, { visits: number }>();

function trackVisit(el: HTMLElement) {
  const data = elementData.get(el) ?? { visits: 0 };
  data.visits++;
  elementData.set(el, data);
}

// 元素被移除后,对应数据自动回收

WeakSet

// WeakSet:对象集合,弱引用
const seen = new WeakSet<object>();

function markProcessed(obj: object) {
  seen.add(obj);
}

function isProcessed(obj: object): boolean {
  return seen.has(obj);
}

Map/Set vs WeakMap/WeakSet

对比项Map/SetWeakMap/WeakSet
键类型任意只能是对象
引用强引用弱引用
遍历✅❌
适用常规数据缓存、元数据
💡 提示

用 WeakMap 存"对象关联数据"(如 DOM 元数据、缓存)能避免内存泄漏,是性能敏感场景的首选。

TypeScript 元组

掌握 TypeScript 元组(Tuple):固定长度与类型的数组。

基本元组

// 固定结构:[string, number]
let tuple: [string, number] = ["hello", 42];

tuple[0];           // string
tuple[1];           // number
// tuple[0] = 42    // ❌ 类型错误

// 长度固定
// tuple[2] = true  // ❌ 越界报错

元组与解构

// 元组解构(最常见的用途)
function getCoordinates(): [number, number] {
  return [100, 200];
}

const [x, y] = getCoordinates();
// x: number, y: number

// useState 的返回类型就是元组
const [count, setCount] = useState<number>(0);
// 类型为 [number, Dispatch<SetStateAction<number>>]

可选与剩余元组

// 可选元素
type Pair = [string, number?];
const p1: Pair = ["hello"];         // ✅
const p2: Pair = ["hello", 42];     // ✅

// 剩余元素
type Rest = [string, ...number[]];
const r1: Rest = ["start"];                    // ✅
const r2: Rest = ["start", 1, 2, 3];           // ✅

// 命名元组
type Named = [x: number, y: number];
const point: Named = [10, 20];
// IDE 会提示参数名 x / y

标签元组

// 标签增强可读性
type Result = [success: boolean, message: string, data?: unknown];

function run(): Result {
  return [true, "操作成功"];
}

const [ok, msg] = run();
// IDE 显示:ok: boolean, msg: string

元组 vs 对象

对比项元组对象
定义[string, number]{ name: string; age: number }
可读性按位置按字段名(更好)
适用临时配对、解构返回值复杂结构化数据
💡 提示

元组适合"位置语义清晰"的短数据(坐标、键值对、useState 返回值);字段多于 3 个建议用对象或接口。

TypeScript 联合类型

掌握 TypeScript 联合类型(Union):一个值可以是多种类型之一。

基础联合

// 变量可以是 string 或 number
let id: string | number = "abc";
id = 42;        // ✅

// 函数参数
function printId(id: string | number) {
  console.log(id);
}

联合 + 类型收窄

function formatId(id: string | number): string {
  // typeof 收窄
  if (typeof id === "string") {
    return id.toUpperCase();
  }
  return id.toFixed(0);
}

formatId("abc");   // "ABC"
formatId(42.5);    // "43"

可辨识联合(Discriminated Union)

// 用共同的"标签字段"区分
interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  side: number;
}

interface Triangle {
  kind: "triangle";
  base: number;
  height: number;
}

type Shape = Circle | Square | Triangle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
    case "triangle":
      return (shape.base * shape.height) / 2;
    default:
      const _exhaustive: never = shape;   // 穷尽检查
      return _exhaustive;
  }
}

联合类型与数组

// 元素可以是多种类型
const items: (string | number)[] = [1, "two", 3];

// 过滤收窄
const nums = items.filter((i): i is number => typeof i === "number");
// number[]

联合类型工具

// 从数组提取联合
const colors = ["red", "green", "blue"] as const;
type Color = typeof colors[number];
// "red" | "green" | "blue"

// 可空联合(常见模式)
type MaybeUser = User | null | undefined;

// 联合 vs 交叉
type A = { a: number };
type B = { b: string };
type Union = A | B;      // A 或 B
type Inter = A & B;      // A 且 B(都有)
💡 提示

可辨识联合(标签字段 + switch)是 TS 最实用的模式之一:类型安全 + 穷尽检查 + 可读性好。

TypeScript 接口

掌握 TypeScript 接口(Interface):定义对象结构的核心工具。

基础接口

interface User {
  id: number;
  name: string;
  email?: string;             // 可选属性
  readonly createdAt: Date;   // 只读属性
}

function createUser(data: User): void {
  console.log(data.name);
}

const user: User = {
  id: 1,
  name: "张三",
  createdAt: new Date(),
};

接口继承

interface Animal {
  name: string;
  eat(): void;
}

interface Dog extends Animal {
  breed: string;        // 新增
  bark(): void;
}

const dog: Dog = {
  name: "旺财",
  breed: "金毛",
  eat() {},
  bark() {},
};

接口与类

// 类实现接口
interface Shape {
  getArea(): number;
}

class Rectangle implements Shape {
  constructor(private w: number, private h: number) {}

  getArea(): number {
    return this.w * this.h;
  }
}

// 接口约束构造器(工厂模式)
interface ShapeConstructor {
  new (w: number, h: number): Shape;
}

function createShape(Ctor: ShapeConstructor, w: number, h: number): Shape {
  return new Ctor(w, h);
}

索引签名

interface Dict {
  [key: string]: number;    // 任意字符串键,值为 number
}

const scores: Dict = {
  zhangsan: 90,
  lisi: 85,
};

// 数组式访问
scores["wangwu"] = 88;

函数类型接口

interface BinaryOp {
  (a: number, b: number): number;
}

const add: BinaryOp = (a, b) => a + b;
const multiply: BinaryOp = (a, b) => a * b;

接口 vs type 别名

对比项interfacetype
扩展extends& 交叉
合并✅ 同名自动合并❌
适用对象/类结构联合/工具类型/复杂类型
💡 提示

经验法则:定义对象/类结构用 interface,组合类型(联合、映射、条件)用 type。

TypeScript 类

掌握 TypeScript 类(Class):面向对象编程的核心。

基础类

class Person {
  // 属性声明 + 类型
  name: string;
  age: number;

  // 构造器
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 方法
  greet(): string {
    return `你好,我是 ${this.name}`;
  }
}

const p = new Person("张三", 25);
console.log(p.greet());   // 你好,我是 张三

参数属性(简写)

// 构造器参数自动成为属性
class Person {
  constructor(
    public name: string,
    public age: number,
    private secret: string,
  ) {}
}

const p = new Person("张三", 25, "隐私");
p.name;        // ✅ public 可访问
// p.secret     // ❌ private 不可访问

继承

class Student extends Person {
  school: string;

  constructor(name: string, age: number, school: string) {
    super(name, age);          // 调用父类构造器
    this.school = school;
  }

  // 覆盖父类方法
  greet(): string {
    return `${super.greet()},就读于 ${this.school}`;
  }
}

getter/setter

class Temperature {
  private _celsius = 0;

  get celsius(): number {
    return this._celsius;
  }

  set celsius(value: number) {
    this._celsius = value;
  }

  get fahrenheit(): number {
    return this._celsius * 9 / 5 + 32;
  }
}

const t = new Temperature();
t.celsius = 25;
console.log(t.fahrenheit);   // 77

静态成员

class Config {
  static version = "1.0.0";
  static DEFAULT_TIMEOUT = 5000;

  static getInfo(): string {
    return `版本 ${this.version}`;
  }
}

Config.version;              // "1.0.0"
Config.getInfo();            // "版本 1.0.0"

抽象类

abstract class Shape {
  abstract getArea(): number;   // 抽象方法:子类必须实现

  describe(): string {
    return `面积:${this.getArea()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) { super(); }

  getArea(): number {
    return Math.PI * this.radius ** 2;
  }
}

// const s = new Shape();  // ❌ 抽象类不能实例化
const c = new Circle(5);
console.log(c.describe());
💡 提示

TS 类 = JS 类 + 类型:属性声明、访问修饰符(public/private/protected)、抽象类是主要的增量。

TypeScript 对象

掌握 TypeScript 对象类型:对象的结构化类型系统。

对象类型定义

// 内联对象类型
const point: { x: number; y: number } = { x: 10, y: 20 };

// 接口
interface Config {
  theme: string;
  debug: boolean;
}

// type 别名
type User = {
  id: number;
  name: string;
};

// 三种方式等价,都定义对象形状

对象的可选与只读

interface Settings {
  theme: string;
  lang?: string;              // 可选
  readonly id: number;        // 只读
  [key: string]: unknown;     // 索引签名(其他任意字段)
}

const s: Settings = {
  theme: "dark",
  id: 1,
  customField: "任意值",
};
// s.id = 2  // ❌ 只读

对象方法

interface Counter {
  count: number;
  increment(): void;          // 方法
  getValue: () => number;     // 函数属性(等价)
}

const counter: Counter = {
  count: 0,
  increment() { this.count++; },
  getValue() { return this.count; },
};

对象合并与类型收窄

// 合并对象
const base = { id: 1 };
const extra = { name: "张三" };
const merged = { ...base, ...extra };
// { id: number; name: string }

// 判断对象类型
interface Cat { meow(): void }
interface Dog { bark(): void }

function describe(pet: Cat | Dog) {
  if ("meow" in pet) {
    pet.meow();
  } else {
    pet.bark();
  }
}

对象与 JSON

interface ApiUser {
  id: number;
  name: string;
  roles: string[];
}

// 解析 JSON(配合断言)
const user = JSON.parse(raw) as ApiUser;

// 序列化
const json = JSON.stringify(user);

// 安全解析
function parseUser(raw: string): ApiUser | null {
  try {
    const data: unknown = JSON.parse(raw);
    if (isApiUser(data)) return data;
    return null;
  } catch {
    return null;
  }
}
💡 提示

对象类型是 TS 最常用的类型——记住三种定义方式(内联/接口/type)和两个修饰符(? 可选、readonly 只读)就够 90% 场景。

TypeScript 泛型

掌握 TypeScript 泛型(Generic):让组件/函数支持多种类型的核心机制。

为什么需要泛型

// 问题:any 丢失类型信息
function identity(arg: any): any {
  return arg;
}
const x = identity("hello");   // x 是 any,无提示

// 解决:泛型保留类型
function identity<T>(arg: T): T {
  return arg;
}
const x = identity("hello");   // x 是 string ✅

泛型函数

// 基础泛型
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

first([1, 2, 3]);        // number | undefined
first(["a", "b"]);       // string | undefined

// 箭头函数泛型
const last = <T,>(arr: T[]): T | undefined => arr[arr.length - 1];

泛型接口

// 泛型接口
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

interface User { id: number; name: string }

const resp: ApiResponse<User[]> = {
  code: 0,
  message: "ok",
  data: [{ id: 1, name: "张三" }],
};

// 泛型类
class Stack<T> {
  private items: T[] = [];

  push(item: T): void { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
}

const numStack = new Stack<number>();
numStack.push(1);

泛型约束

// extends 约束
interface HasLength { length: number }

function getLength<T extends HasLength>(arg: T): number {
  return arg.length;
}

getLength("hello");        // 5
getLength([1, 2, 3]);      // 3
// getLength(42)           // ❌ number 无 length

// 泛型 + keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "张三" };
getProperty(user, "name");   // string
// getProperty(user, "age")  // ❌ "age" 不在 user 中

泛型默认值

interface Response<T = any> {
  data: T;
}

const r: Response = { data: "默认" };        // T 默认 any
const r2: Response<number> = { data: 42 };
💡 提示

泛型的核心:定义时不指定类型,使用时才确定。它让"通用组件"(列表、请求封装、缓存)既通用又类型安全。

TypeScript 命名空间

掌握 TypeScript 命名空间(Namespace):组织代码的旧方式。

为什么讲命名空间

命名空间是 TS 早期(ES Module 前)的代码组织方式。现在推荐用 ES Module(import/export),但理解命名空间有助于阅读旧代码。

基础命名空间

namespace Utils {
  export function format(n: number): string {
    return n.toFixed(2);
  }

  export const PI = 3.14159;

  export interface Options {
    precision: number;
  }
}

// 使用
Utils.format(3.14159);    // "3.14"
Utils.PI;                 // 3.14159

嵌套命名空间

namespace App {
  export namespace Config {
    export const VERSION = "1.0";
  }

  export namespace Utils {
    export function log(msg: string): void {
      console.log(`[App] ${msg}`);
    }
  }
}

App.Config.VERSION;
App.Utils.log("hello");

跨文件命名空间

// 文件1: logger.ts
namespace Logger {
  export function info(msg: string) { console.log(msg); }
}

// 文件2: app.ts(需要三斜线引用)
/// <reference path="./logger.ts" />
namespace App {
  export function start() {
    Logger.info("启动");
  }
}

命名空间 vs 模块

对比项命名空间ES 模块
语法namespace X {}import/export
加载全局对象按需加载
推荐度旧代码/内部组织✅ 现代标准
Tree-shaking不支持支持

模块替代方案

// utils.ts(推荐用模块替代命名空间)
export const PI = 3.14159;

export function format(n: number): string {
  return n.toFixed(2);
}

export interface Options {
  precision: number;
}

// 使用
import { PI, format, Options } from "./utils";
💡 提示

新代码一律用 ES Module(import/export);命名空间只在阅读 .d.ts 声明文件或旧项目时遇到。

TypeScript 模块

掌握 TypeScript 模块:import/export 的组织与类型导入。

导出与导入

// math.ts
export const PI = 3.14159;

export function square(n: number): number {
  return n * n;
}

export interface Point {
  x: number;
  y: number;
}

// 默认导出
export default class Calculator { /* ... */ }
// app.ts
import Calculator, { PI, square, Point } from "./math";

// 整体导入
import * as math from "./math";
math.PI;

// 重命名
import { square as sq } from "./math";

// 类型导入(推荐:编译期擦除)
import type { Point } from "./math";

重新导出

// index.ts:统一出口
export { PI, square } from "./math";
export type { Point } from "./math";
export * from "./utils";
export { default as Calculator } from "./math";

模块解析与路径

// 相对路径
import { util } from "./utils/helper";
import { util } from "../utils/helper";

// 别名(tsconfig paths)
import { util } from "@utils/helper";

// 包导入
import express from "express";
import { useState } from "react";

模块类型(.d.ts)

// types.d.ts
declare module "my-module" {
  export function doSomething(): void;
  export interface Options { debug: boolean }
}

// 使用时获得类型
import { doSomething, Options } from "my-module";

动态导入

// 按需加载(代码分割)
async function loadModule() {
  const { heavyFn } = await import("./heavy");
  heavyFn();
}

// 条件加载
if (condition) {
  const { chart } = await import("./chart");
  chart.render();
}
💡 提示

类型导入用 import type:编译后不产生运行时代码;动态 import() 用于代码分割,减小首屏体积。

TypeScript 声明文件

掌握 TypeScript 声明文件(.d.ts):为 JS 库提供类型。

什么是声明文件

.d.ts 文件只包含类型声明,没有实现代码。它告诉 TypeScript"这个 JS 库有什么类型",编译时被擦除。

// 目录结构
my-lib/
├── index.js       # 实际代码(JS)
└── index.d.ts     # 类型声明(只声明不实现)

编写声明文件

// index.d.ts
export interface User {
  id: number;
  name: string;
}

export function getUser(id: number): User;

export const VERSION: string;

export default class API {
  constructor(baseUrl: string);
  fetch(path: string): Promise<unknown>;
}

全局声明

// global.d.ts
// 声明全局变量
declare const APP_VERSION: string;

// 声明全局函数
declare function trackEvent(name: string, data?: object): void;

// 声明全局类型
interface Window {
  myCustomProp?: string;
}

使用现有类型包

# 安装库的类型声明
npm install -D @types/node
npm install -D @types/react
npm install -D @types/express
// 很多流行库自带类型(@types 或内置)
import axios from "axios";        // 自带类型
import express from "express";    // @types/express

声明自己的模块

// 为无类型的 JS 文件补充声明
// js-module.d.ts
declare module "*.jpg" {
  const src: string;
  export default src;
}

declare module "*.css" {
  const classes: Record<string, string>;
  export default classes;
}

declare module "my-old-js-lib";   // 简单声明:any 类型

发布带类型的库

// package.json
{
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts"
}
# tsconfig 自动生成声明文件
"declaration": true
💡 提示

90% 场景不需要自己写 .d.ts——主流库都有类型。只有在"给 JS 库补类型"或"发布库"时才需要。

TypeScript 测验

本章测验检验你对 TypeScript 的掌握。先独立作答,再看解析。

选择题

1. 定义"可空字符串"的正确类型?

  • A. string
  • B. string | null ✅
  • C. string?
  • D. null | undefined

2. 泛型函数 identity<T>(arg: T): T 的用途?

  • A. 只能传数字
  • B. 保持传入类型不变 ✅
  • C. 总是返回字符串
  • D. 不返回任何值

3. 下列哪个是"只读"属性修饰符?

  • A. readonly ✅
  • B. static
  • C. const
  • D. final

4. unknown 与 any 的区别?

  • A. 完全相同
  • B. unknown 使用前必须收窄类型 ✅
  • C. any 更严格
  • D. unknown 不能赋值

5. 接口 interface A extends B 的含义?

  • A. A 与 B 无关
  • B. A 继承 B 的成员 ✅
  • C. A 是 B 的实例
  • D. 报错

6. 元组 [string, number] 表示?

  • A. 字符串数组
  • B. 第一个元素是字符串,第二个是数字 ✅
  • C. 任意数组
  • D. 数字数组

判断题

7. TypeScript 代码会被编译成 JavaScript。

✅ 正确。tsc 编译器输出纯 JS,类型信息被擦除。

8. strict: true 会开启严格空值检查。

✅ 正确。strict 包含 strictNullChecks 等多项检查。

简答题

9. 写出联合类型与交叉类型的区别。

type A = { a: number };
type B = { b: string };
type Union = A | B;    // 是 A 或 B(二选一)
type Inter = A & B;    // 同时有 a 和 b(都有)

10. 写一个带泛型的函数(返回数组第一个元素)。

function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

计分

答对水平
9-10优秀,可投入实战
6-8良好,查漏补缺
<6建议重学基础类型与接口章节
💡 提示

TS 学习的终极检验:把旧 JS 项目迁移到 TS,你会快速掌握所有类型概念。

TypeScript type 别名

掌握 TypeScript type 别名:为复杂类型命名,提高可读性。

基础类型别名

// 基础类型
type ID = number;
type Name = string;

// 联合类型(type 的强项)
type Status = "active" | "inactive" | "pending";
type Result = string | null;
type IDOrName = number | string;

// 对象类型
type Point = {
  x: number;
  y: number;
};

函数类型别名

// 函数签名
type Handler = (event: Event) => void;
type Calculator = (a: number, b: number) => number;

const add: Calculator = (a, b) => a + b;

// 可选参数
type Logger = (message: string, level?: "info" | "error") => void;

元组类型别名

type Coordinate = [number, number];
type HttpResult = [status: number, body: string];

const point: Coordinate = [10, 20];
const res: HttpResult = [200, "ok"];

泛型类型别名

type Box<T> = { value: T };
type Result<T> = { success: true; data: T } | { success: false; error: string };

const box: Box<number> = { value: 42 };
const ok: Result<User> = { success: true, data: user };
const fail: Result<User> = { success: false, error: "出错" };

工具类型组合

interface User { id: number; name: string; email: string }

type UserWithoutEmail = Omit<User, "email">;
type UserWithOnlyName = Pick<User, "name">;
type OptionalUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserArray = User[];

// 条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>;   // true
type B = IsString<number>;   // false

type vs interface

对比项typeinterface
联合/交叉✅ 原生支持❌ 需借助 type
元组/函数✅可但别扭
声明合并❌✅
extends❌(用 &)✅
推荐组合类型对象/类结构
💡 提示

别名让"长类型"变短:定义一次 Status,全项目复用,修改只需改一处。

TypeScript 字面量类型

掌握 TypeScript 字面量类型:精确到具体值的类型。

字符串字面量

// 具体字符串作为类型
type Direction = "up" | "down" | "left" | "right";

function move(direction: Direction) {
  console.log(`向${direction}移动`);
}

move("up");          // ✅
// move("north")     // ❌ 不在联合中

// 常见用法:状态机
type Status = "pending" | "processing" | "done" | "failed";

数字字面量

// 具体数字作为类型
type Port = 80 | 443 | 8080;

const p: Port = 443;   // ✅
// const p2: Port = 3000;  // ❌

// 版本号
type Version = 1 | 2 | 3;

布尔字面量

// true / false 也是字面量类型
let isOn: true = true;
// isOn = false  // ❌

// 与联合组合
type Toggle = true | false;   // 等价 boolean

字面量推断与 as const

// 普通 const:推断为字面量
const method = "GET";        // 类型 "GET"

// let 推断为宽类型
let method2 = "GET";         // 类型 string

// as const:锁死
const config = {
  api: "/api",
  retries: 3,
} as const;
// config.api 类型是 "/api",config.retries 是 3

字面量 + 泛型

// 从配置数组提取字面量联合
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE"] as const;
type HttpMethod = typeof HTTP_METHODS[number];
// "GET" | "POST" | "PUT" | "DELETE"

function request(method: HttpMethod, url: string) {
  // ...
}

request("GET", "/api");    // ✅
// request("PATCH", "/api")  // ❌

字面量类型的好处

  • 错误提前:非法值编译期报错
  • 自动补全:IDE 提示可选值
  • 文档化:类型即取值范围说明
💡 提示

字面量联合类型是"枚举的现代替代":type Status = "a" | "b" | "c" 比 enum 更简洁、无运行时开销。

TypeScript 抽象类

掌握 TypeScript 抽象类(Abstract Class):定义"骨架",子类填充细节。

基础抽象类

abstract class Animal {
  // 普通属性
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  // 抽象方法:子类必须实现
  abstract makeSound(): void;

  // 普通方法:可继承复用
  move(distance: number): void {
    console.log(`${this.name} 移动了 ${distance} 米`);
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log("汪汪!");
  }
}

// const a = new Animal("动物");  // ❌ 抽象类不能实例化
const dog = new Dog("旺财");
dog.makeSound();    // 汪汪!
dog.move(10);       // 旺财 移动了 10 米

抽象属性与抽象方法

abstract class Report {
  // 抽象属性
  abstract title: string;

  // 抽象方法(可带参数)
  abstract generate(start: Date, end: Date): string;

  // 模板方法模式:定义骨架
  print(): void {
    console.log(`报告:${this.title}`);
    console.log(this.generate(new Date(), new Date()));
  }
}

class SalesReport extends Report {
  title = "销售报告";

  generate(start: Date, end: Date): string {
    return `统计 ${start} 到 ${end} 的销售额`;
  }
}

抽象类 vs 接口

对比项抽象类接口
实现代码✅ 可以有❌ 只有声明
属性✅✅
继承单继承可多实现
运行时存在✅ 有构造器❌ 编译期擦除
适用共享实现的基类定义形状契约

抽象类实战:表单校验器

abstract class Validator {
  abstract validate(value: unknown): boolean;
  abstract get message(): string;

  // 模板方法
  check(value: unknown): { valid: boolean; message: string } {
    return {
      valid: this.validate(value),
      message: this.message,
    };
  }
}

class EmailValidator extends Validator {
  validate(value: unknown): boolean {
    return typeof value === "string" && /^\S+@\S+\.\S+$/.test(value);
  }
  get message(): string {
    return "邮箱格式不正确";
  }
}

class RequiredValidator extends Validator {
  validate(value: unknown): boolean {
    return value !== null && value !== undefined && value !== "";
  }
  get message(): string {
    return "该字段必填";
  }
}
💡 提示

抽象类适合"基类有共享实现 + 部分需要子类定制"的场景(模板方法模式);纯契约用接口。

TypeScript 访问修饰符

掌握 TypeScript 访问修饰符:public、private、protected 与 readonly。

三种访问级别

class Person {
  public name: string;       // 默认 public:任何地方可访问
  private secret: string;    // private:仅类内部
  protected hobby: string;   // protected:类内部 + 子类

  constructor(name: string, secret: string, hobby: string) {
    this.name = name;
    this.secret = secret;
    this.hobby = hobby;
  }

  private logSecret(): void {
    console.log(this.secret);   // ✅ 内部可访问
  }
}

const p = new Person("张三", "隐私", "读书");
p.name;              // ✅
// p.secret          // ❌ private
// p.hobby           // ❌ protected

class Student extends Person {
  showHobby(): string {
    return this.hobby;   // ✅ 子类可访问 protected
  }
  // showSecret() { return this.secret; }  // ❌ 不可访问 private
}

readonly

class Config {
  readonly version = "1.0.0";   // 初始化后不可改
  readonly id: number;

  constructor(id: number) {
    this.id = id;    // 构造器内可赋值
  }
}

const c = new Config(1);
// c.version = "2.0"  // ❌ 只读

参数属性简写

// 声明 + 赋值一步完成
class User {
  constructor(
    public id: number,
    public name: string,
    private password: string,
    protected role: string = "user",
    readonly createdAt: Date = new Date(),
  ) {}
}

const u = new User(1, "张三", "123456");
u.id;               // ✅ public
// u.password       // ❌ private

JS 私有字段(#)

// TS 3.8+ 支持 JS 原生 # 私有
class BankAccount {
  #balance = 0;            // 真正的私有(运行时也私有)

  deposit(amount: number): void {
    this.#balance += amount;
  }

  getBalance(): number {
    return this.#balance;
  }
}

// # 是硬私有:编译后仍是私有,任何方式都访问不到

访问修饰符选择

修饰符访问范围用途
public任何地方对外 API(默认)
private类内部内部实现细节
protected类 + 子类子类可用的工具方法
readonly只读不可变数据
💡 提示

设计原则:默认 private,需要对外才 public。暴露的 API 越少,后续改动越自由。

TypeScript 混入

掌握 TypeScript 混入(Mixin):组合多个类的能力。

为什么需要混入

TS 类单继承(一个类只能 extends 一个父类)。混入通过组合方式"复制"多个类的方法,实现多继承效果。

基础混入

// 定义混入类
class Timestampable {
  createdAt = new Date();

  getCreatedTime(): string {
    return this.createdAt.toISOString();
  }
}

class Serializable {
  toJSON(): string {
    return JSON.stringify(this);
  }
}

// 应用混入
type Constructor<T = {}> = new (...args: any[]) => T;

function Timestampable<T extends Constructor>(Base: T) {
  return class extends Base {
    createdAt = new Date();
    getCreatedTime(): string {
      return this.createdAt.toISOString();
    }
  };
}

function Serializable<T extends Constructor>(Base: T) {
  return class extends Base {
    toJSON(): string {
      return JSON.stringify(this);
    }
  };
}

class Entity {}
const Enhanced = Serializable(Timestampable(Entity));

const e = new Enhanced();
e.getCreatedTime();   // ✅
e.toJSON();           // ✅

带类型的混入

// 泛型混入保留类型
function WithLogging<T extends Constructor>(Base: T) {
  return class extends Base {
    log(method: string): void {
      console.log(`调用:${method}`);
    }
  };
}

// 组合
const EnhancedEntity = WithLogging(Serializable(Timestampable(Entity)));
type EnhancedEntityInstance = InstanceType<typeof EnhancedEntity>;

混入 vs 继承 vs 组合

方式特点适用
继承单父类,is-a 关系明确层级
混入组合能力,has-a 效果横切功能(日志/序列化)
组合持有对象,委托调用大多数场景(推荐)

实用建议

// 大多数"混入"需求可用组合替代
class Service {
  constructor(private logger: Logger, private serializer: Serializer) {}

  run(): void {
    this.logger.log("运行");
    this.serializer.serialize(this);
  }
}
💡 提示

混入适合"给多个不相关类添加同一能力";能用组合就优先组合,代码更直白。

TypeScript 装饰器

掌握 TypeScript 装饰器(Decorator):用注解增强类/方法/属性的能力。

启用装饰器

// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

类装饰器

// 类装饰器:接收构造函数
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

function version(ver: string) {
  return function (constructor: Function) {
    constructor.prototype.version = ver;
  };
}

@sealed
@version("1.0.0")
class Api {
  // ...
}

const api = new Api();
// (api as any).version  // "1.0.0"

方法装饰器

// 方法装饰器
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`调用 ${key},参数:`, args);
    const result = original.apply(this, args);
    console.log(`返回:`, result);
    return result;
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

new Calculator().add(1, 2);
// 调用 add,参数: [1, 2]
// 返回: 3

属性装饰器

function readonly(target: any, key: string) {
  // 修改属性描述符
  Object.defineProperty(target, key, { writable: false });
}

class Config {
  @readonly
  version = "1.0";
}

// 运行时验证
const c = new Config();
// c.version = "2.0"  // 严格模式下报错或静默失败

参数装饰器

function required(target: any, key: string, index: number) {
  // 标记参数为必填
  console.log(`参数 ${key}[${index}] 是必填`);
}

class User {
  save(@required name: string) {
    // ...
  }
}

装饰器的实战应用

// 依赖注入框架(如 Angular、NestJS)大量使用装饰器
function Injectable() {
  return (target: any) => {
    Reflect.defineMetadata("injectable", true, target);
  };
}

function Inject(token: string) {
  return (target: any, key: string, index: number) => {
    // 记录依赖
  };
}

@Injectable()
class UserService {
  constructor(@Inject("DB") private db: any) {}
}
⚠️ 注意

装饰器目前是实验特性(experimentalDecorators),新标准(TC39 decorators)正在演进。用前确认框架要求(Angular/NestJS 必需)。

TypeScript 类型守卫

掌握 TypeScript 类型守卫(Type Guards):运行时检查 + 编译期收窄。

typeof 守卫

function process(input: string | number | boolean) {
  if (typeof input === "string") {
    return input.toUpperCase();       // string
  }
  if (typeof input === "number") {
    return input.toFixed(2);          // number
  }
  return input ? "yes" : "no";        // boolean
}

// typeof 支持的类型:
// "string" | "number" | "boolean" | "bigint"
// "symbol" | "undefined" | "object" | "function"

instanceof 守卫

function format(value: Date | RegExp | string) {
  if (value instanceof Date) {
    return value.toISOString();
  }
  if (value instanceof RegExp) {
    return value.source;
  }
  return value;   // string
}

in 守卫(属性检查)

interface Bird { fly(): void }
interface Fish { swim(): void }

function move(pet: Bird | Fish) {
  if ("fly" in pet) {
    pet.fly();       // Bird
  } else {
    pet.swim();      // Fish
  }
}

自定义类型谓词

// pet is Fish 是"类型谓词"
function isFish(pet: Bird | Fish): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

const pets: (Bird | Fish)[] = [/* ... */];
const fishes = pets.filter(isFish);   // Fish[] ✅

// 谓词函数在 filter/map 中很好用
const strings: (string | null)[] = ["a", null, "b"];
const clean = strings.filter((s): s is string => s !== null);  // string[]

可辨识联合守卫

type Action =
  | { type: "ADD"; payload: number }
  | { type: "REMOVE"; id: number }
  | { type: "RESET" };

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case "ADD":
      return state + action.payload;   // 收窄到 ADD
    case "REMOVE":
      return state - action.id;        // 收窄到 REMOVE
    case "RESET":
      return 0;
    default:
      return state;
  }
}

类型守卫的收益

  • 运行时安全:真正检查数据
  • 编译期收窄:后续代码类型精确
  • 可复用:谓词函数到处用
💡 提示

处理"外部数据"(API 响应、localStorage、用户输入)时,类型守卫比断言(as)安全得多。

TypeScript 可选链

掌握 TypeScript 可选链(Optional Chaining):安全访问嵌套属性。

基础用法

interface User {
  profile?: {
    address?: {
      city?: string;
    };
  };
}

const user: User = {};

// 可选链:任一环节为空就短路返回 undefined
const city = user.profile?.address?.city;
// undefined(不会报错)

// 等价老写法
const city2 = user.profile && user.profile.address && user.profile.address.city;

可选调用

interface Config {
  onReady?: () => void;
}

const config: Config = {};

// 只有存在才调用
config.onReady?.();
// 等价:
// if (config.onReady) config.onReady();

// 带参数
config.onReady?.("param");

可选索引

// 数组安全访问
const arr: number[] = [];
const first = arr[0]?.toFixed();   // undefined 而非报错

// 对象属性
const data: Record<string, any> = {};
const value = data["key"]?.nested;

// 配合空值合并
const city = user.profile?.address?.city ?? "未知城市";

与空值合并连用

// 经典组合:安全访问 + 默认值
const name = user.profile?.name ?? "匿名";
const age = user.age ?? 0;

// 嵌套 + 函数
const result = getConfig()?.timeout ?? 5000;

// 链式安全调用
const data = api.getUser?.()?.data ?? null;

注意事项

场景说明
不能用于赋值obj?.x = 1 非法
短路前半为 null 时,后面表达式不执行
与 ?? 搭配常一起用:a?.b ?? default
💡 提示

可选链(?.)+ 空值合并(??)是 TS/JS 处理嵌套空值的黄金组合,代码可读性提升一个档次。

TypeScript 工具类型

掌握 TypeScript 工具类型(Utility Types):内置的常用类型变换。

Partial / Required

interface User {
  id: number;
  name: string;
  email: string;
}

// 全部可选
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }

// 全部必填
type RequiredUser = Required<Partial<User>>;

Pick / Omit

// 挑选部分字段
type UserName = Pick<User, "id" | "name">;

// 排除部分字段(常用于隐藏敏感字段)
type PublicUser = Omit<User, "email">;

// 实际应用:响应不返回密码
interface DbUser {
  id: number;
  name: string;
  password: string;
}
type SafeUser = Omit<DbUser, "password">;

Readonly / Record

// 全部只读
type ReadonlyUser = Readonly<User>;
// ReadonlyUser.id = 2  // ❌

// Record:键值映射
type StatusMap = Record<"active" | "inactive", string>;
const map: StatusMap = {
  active: "启用",
  inactive: "停用",
};

// Record 构造字典
type Config = Record<string, number>;
const config: Config = { timeout: 5000, retries: 3 };

Extract / Exclude

// 从联合中提取
type A = "a" | "b" | "c";
type Extracted = Extract<A, "a" | "c">;   // "a" | "c"

// 从联合中排除
type Excluded = Exclude<A, "b">;          // "a" | "c"

// 排除 null/undefined
type NonNullableStr = NonNullable<string | null | undefined>;  // string

ReturnType / Parameters

// 获取函数返回类型
function fetchUser(): Promise<User> { /* ... */ }
type FetchResult = ReturnType<typeof fetchUser>;   // Promise<User>

// 获取参数类型
type FetchParams = Parameters<typeof fetchUser>;   // []

// 实战:类型安全的 async 封装
type AsyncReturn<T extends (...args: any) => any> = Awaited<ReturnType<T>>;

Awaited(解包 Promise)

type Data = Awaited<Promise<User>>;   // User
type Deep = Awaited<Promise<Promise<number>>>;  // number

工具类型速查表

工具作用
Partial<T>全部可选
Required<T>全部必填
Readonly<T>全部只读
Pick<T, K>选字段
Omit<T, K>删字段
Record<K, V>键值映射
Extract / Exclude联合提取/排除
ReturnType / Parameters函数类型查询
💡 提示

工具类型就是内置的泛型类型:Omit<T, "password"> 一条就搞定"响应脱敏",非常实用。

TypeScript 条件类型

掌握 TypeScript 条件类型(Conditional Types):根据类型条件选择不同类型。

基础语法

// T extends U ? X : Y
// 如果 T 可赋值给 U,结果是 X,否则是 Y

type IsString<T> = T extends string ? true : false;

type A = IsString<string>;   // true
type B = IsString<number>;   // false
type C = IsString<"hello">;  // true(字符串字面量也 extends string)

条件类型实战

// 根据参数类型返回不同处理
type Process<T> = T extends number
  ? (n: number) => number
  : T extends string
    ? (s: string) => string
    : never;

const fn: Process<number> = (n) => n * 2;
const strFn: Process<string> = (s) => s.toUpperCase();

分布式条件类型

// 联合类型会分发(逐个判断)
type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>;
// string[] | number[](分发给每个成员)
// 而非 (string | number)[]

// 用方括号包裹避免分发
type NoDistribute<T> = [T] extends [any] ? T[] : never;
type Result2 = NoDistribute<string | number>;
// (string | number)[]

infer 关键字

// infer:在条件类型中"提取"类型
type ElementOf<T> = T extends (infer E)[] ? E : never;

type A = ElementOf<number[]>;   // number
type B = ElementOf<string[]>;   // string

// 提取 Promise 内部类型
type Unwrap<T> = T extends Promise<infer U> ? U : T;

type X = Unwrap<Promise<string>>;   // string
type Y = Unwrap<number>;            // number

内置条件类型

// Exclude:排除类型
type T1 = Exclude<"a" | "b" | "c", "a">;   // "b" | "c"

// Extract:提取类型
type T2 = Extract<"a" | "b" | "c", "a" | "b">;  // "a" | "b"

// NonNullable:排除 null/undefined
type T3 = NonNullable<string | null | undefined>;  // string

// 它们都是条件类型实现的

条件类型应用

// 类型安全的 API 响应
type ApiResult<T> = T extends { code: 0 }
  ? { success: true; data: T["data"] }
  : { success: false; error: string };

type Success = { code: 0; data: User };
type Failure = { code: 500; message: string };

const ok: ApiResult<Success> = { success: true, data: user };
const fail: ApiResult<Failure> = { success: false, error: "服务器错误" };
💡 提示

条件类型 + infer 是 TS 类型编程的"逻辑运算",配合映射类型可以写出非常强大的通用类型工具。

TypeScript 映射类型

掌握 TypeScript 映射类型(Mapped Types):批量变换对象类型的属性。

基础映射

interface User {
  id: number;
  name: string;
  email: string;
}

// 遍历所有键,生成新类型
type Options<T> = {
  [K in keyof T]: boolean;
};

type UserOptions = Options<User>;
// { id: boolean; name: boolean; email: boolean }

内置映射类型原理

// Partial:全部可选
type Partial<T> = {
  [K in keyof T]?: T[K];
};

// Readonly:全部只读
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

// 使用内置工具即可
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;

键修饰符

// + / - 添加/移除修饰符
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];    // 移除 readonly
};

type Required2<T> = {
  [K in keyof T]-?: T[K];            // 移除可选
};

interface Config {
  readonly host: string;
  port?: number;
}

type MutableConfig = Mutable<Config>;
// { host: string; port?: number }(readonly 被移除)

重映射键(TS 4.1+)

// as 子句:重命名键
type Getters<T> = {
  [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};

interface Person {
  name: string;
  age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }

// 过滤键
type OnlyStringKeys<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

interface Mixed {
  name: string;
  age: number;
  email: string;
}
// OnlyStringKeys → { name: string; email: string }

映射 + 条件类型组合

// 深层可选
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Nested {
  user: { name: string; settings: { dark: boolean } };
}

const data: DeepPartial<Nested> = {
  user: { settings: { dark: true } },   // 任意层级都可选
};
💡 提示

映射类型公式:{ [K in keyof T]: 变换 }——像"数组 map"一样处理类型,是高级类型的核心技巧。

TypeScript 类继承与多态

掌握 TypeScript 类继承与多态:面向对象设计在 TS 中的落地。

继承基础

class Animal {
  constructor(public name: string) {}

  speak(): string {
    return `${this.name} 发出声音`;
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);          // 必须调用父类构造器
  }

  speak(): string {       // 方法覆盖(多态)
    return `${this.name} 汪汪叫`;
  }
}

class Cat extends Animal {
  speak(): string {
    return `${this.name} 喵喵叫`;
  }
}

const animals: Animal[] = [new Dog("旺财"), new Cat("咪咪")];

// 多态:同一方法,不同表现
animals.forEach(a => console.log(a.speak()));
// 旺财 汪汪叫
// 咪咪 喵喵叫

protected 与继承

class Vehicle {
  protected speed = 0;    // 子类可访问

  protected setSpeed(s: number): void {
    this.speed = s;
  }

  getSpeed(): number {
    return this.speed;
  }
}

class Car extends Vehicle {
  accelerate(): void {
    this.setSpeed(this.speed + 20);   // ✅ protected 可访问
  }
}

const car = new Car();
car.accelerate();
car.getSpeed();    // 20
// car.speed       // ❌ 外部不可访问

方法覆盖与 super

class Base {
  greet(): string {
    return "你好";
  }
}

class Extended extends Base {
  greet(): string {
    // 调用父类方法再扩展
    return super.greet() + ",欢迎来到 CodeNest!";
  }
}

new Extended().greet();   // 你好,欢迎来到 CodeNest!

多态实战:形状系统

abstract class Shape {
  abstract area(): number;
  abstract perimeter(): number;

  describe(): string {
    return `面积 ${this.area()},周长 ${this.perimeter()}`;
  }
}

class Circle extends Shape {
  constructor(private radius: number) { super(); }

  area(): number { return Math.PI * this.radius ** 2; }
  perimeter(): number { return 2 * Math.PI * this.radius; }
}

class Rectangle extends Shape {
  constructor(private w: number, private h: number) { super(); }

  area(): number { return this.w * this.h; }
  perimeter(): number { return 2 * (this.w + this.h); }
}

const shapes: Shape[] = [new Circle(5), new Rectangle(4, 6)];
shapes.forEach(s => console.log(s.describe()));

继承设计原则

  • is-a 关系才用继承(Dog 是 Animal)
  • 优先组合(has-a)而非继承
  • 深继承(多层)容易失控,保持 1-2 层
  • 覆盖方法保持签名兼容
💡 提示

多态的核心价值:面向接口/基类编程,调用方不关心具体子类,新类型加入无需改调用代码。

TypeScript 错误处理

掌握 TypeScript 错误处理:异常捕获、类型化错误、最佳实践。

try/catch 基础

function risky(): string {
  try {
    const data = JSON.parse("invalid json");
    return data.name;
  } catch (error) {
    // catch 的参数类型是 unknown(严格模式)
    console.error("解析失败:", error);
    return "默认值";
  }
}

处理 unknown 错误

function safeParse(json: string): unknown {
  try {
    return JSON.parse(json);
  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);    // 安全访问
    } else {
      console.error("未知错误", error);
    }
    return null;
  }
}

// 更完整的错误检查
function handleError(error: unknown): void {
  if (error instanceof Error) {
    console.error("Error:", error.message);
  } else if (typeof error === "string") {
    console.error("String:", error);
  } else {
    console.error("Unknown:", error);
  }
}

自定义错误类

class ApiError extends Error {
  constructor(
    public statusCode: number,
    message: string,
    public details?: unknown,
  ) {
    super(message);
    this.name = "ApiError";
  }
}

class ValidationError extends Error {
  constructor(public fieldErrors: Record<string, string>) {
    super("参数校验失败");
    this.name = "ValidationError";
  }
}

// 抛出
function fetchUser(id: number): User {
  if (id <= 0) {
    throw new ApiError(400, "无效的用户 ID");
  }
  // ...
}

捕获具体错误类型

try {
  await api.request();
} catch (error) {
  if (error instanceof ApiError) {
    // 处理 API 错误
    showToast(`API 错误 ${error.statusCode}:${error.message}`);
  } else if (error instanceof ValidationError) {
    // 处理校验错误
    showErrors(error.fieldErrors);
  } else {
    // 兜底
    console.error(error);
  }
}

错误处理最佳实践

  • catch 参数是 unknown,先判类型再使用
  • 自定义错误类携带上下文(状态码、字段)
  • 底层错误向上抛,顶层统一处理
  • 异步错误用 try/catch 或 .catch()
  • 不要吞掉错误(至少 console.error)
💡 提示

TS 严格模式下 catch 参数是 unknown——这是刻意的设计,逼你先判断错误类型再使用,避免盲目访问属性。

TypeScript 交叉类型

掌握 TypeScript 交叉类型(Intersection Types):合并多个类型。

基础交叉

// & 表示"同时具有"
interface Basic {
  id: number;
  createdAt: Date;
}

interface Timestamped {
  updatedAt: Date;
}

type Entity = Basic & Timestamped;

const entity: Entity = {
  id: 1,
  createdAt: new Date(),
  updatedAt: new Date(),
};

对象合并

type User = { name: string; age: number };
type Address = { city: string; zip: string };

type UserWithAddress = User & Address;

const u: UserWithAddress = {
  name: "张三",
  age: 25,
  city: "北京",
  zip: "100000",
};

函数组合类型

// 交叉函数类型:一个函数满足两个签名
type Logger = (msg: string) => void;
type TimedLogger = (msg: string, time?: Date) => void;

type Combined = Logger & TimedLogger;

const log: Combined = (msg, time?) => {
  console.log(time ?? new Date(), msg);
};

交叉 + 泛型

// 给对象添加通用能力
function withId<T>(obj: T): T & { id: number } {
  return { ...obj, id: Math.random() };
}

const user = withId({ name: "张三" });
user.id;       // number
user.name;     // string

// 类型安全地组合
type WithTimestamps<T> = T & { createdAt: Date };
type AuditableUser = WithTimestamps<User>;

交叉 vs 联合

对比项&(交叉)|(联合)
含义同时具有(并集)其中之一(选择)
示例A & B 有两者字段A | B 是 A 或 B
使用组合能力多态/可选

同名属性冲突

// 同名属性会交叉(可能冲突)
type A = { x: number };
type B = { x: string };

type AB = A & B;
// x 类型为 number & string → 实际是 never(不可用)

// 解决:用不同字段名或 Omit
type SafeAB = Omit<A, "x"> & { x: string };
💡 提示

交叉类型适合"给类型叠加能力"(如加 id、加时间戳);注意同名属性冲突——字段名尽量区分。

TypeScript 模板字面量

掌握 TypeScript 模板字面量类型:用模板语法组合字符串类型。

基础模板字面量

// 用反引号组合字符串类型
type World = "world";
type Greeting = `hello ${World}`;
// "hello world"

// 多个占位符
type Event = "click" | "hover";
type Element = "button" | "link";
type Action = `${Element}-${Event}`;
// "button-click" | "button-hover" | "link-click" | "link-hover"

字符串工具类型

// 内置字符串变换
type Upper = Uppercase<"hello">;       // "HELLO"
type Lower = Lowercase<"ABC">;        // "abc"
type Cap = Capitalize<"hello">;       // "Hello"
type Uncap = Uncapitalize<"Hello">;   // "hello"

// 组合使用
type EventName = `on${Capitalize<"click" | "hover">}`;
// "onClick" | "onHover"

模板字面量实战

// CSS 单位类型
type Size = `${number}px` | `${number}rem` | `${number}%`;
const w: Size = "10px";    // ✅
const h: Size = "50%";     // ✅
// const x: Size = "10em"  // ❌

// 颜色 hex 简版
type HexColor = `#${string}`;
const red: HexColor = "#ff0000";

// 接口路由
type Method = "GET" | "POST" | "PUT" | "DELETE";
type Path = "/users" | "/posts";
type Route = `${Method} ${Path}`;
const r1: Route = "GET /users";    // ✅
// const r2: Route = "PATCH /users"  // ❌

与 keyof 配合

interface User {
  name: string;
  age: number;
}

// 生成 getter 键
type GetterKeys<T> = {
  [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};

type UserGetters = GetterKeys<User>;
// { getName: () => string; getAge: () => number }

推断模板类型

// 从模板提取
type ExtractId<T extends string> =
  T extends `user-${infer Id}` ? Id : never;

type A = ExtractId<"user-123">;   // "123"
type B = ExtractId<"post-1">;     // never

// 解析路由参数
type ParseRoute<T extends string> =
  T extends `/${infer Seg}/${infer Rest}` ? Seg | ParseRoute<`/${Rest}`> : T;

type Segments = ParseRoute<"/users/42/posts">;  // "users" | "42" | "posts"
💡 提示

模板字面量类型 + 映射类型 + infer 是 TS 类型编程的"三件套",常用于框架级类型设计(路由、事件、样式系统)。

TypeScript 从 JavaScript 迁移

把已有的 JavaScript 项目迁移到 TypeScript:渐进式迁移策略。

迁移策略概览

  1. 先搭 TS 环境,保持 JS 可运行
  2. 把 .js 改成 .ts,用宽松配置
  3. 逐步收紧类型(any → 具体类型)
  4. 开启 strict,修复所有错误

第一步:初始化 TS

npm install -D typescript @types/node
npx tsc --init
{
  "compilerOptions": {
    "allowJs": true,              // 允许 JS 文件
    "checkJs": false,             // 不检查 JS(先宽松)
    "strict": false,              // 暂不严格
    "outDir": "./dist",
    "noEmit": true
  },
  "include": ["src"]
}

第二步:JS 逐个转 TS

// 改名前:utils.js
function formatDate(d) {
  return d.toISOString();
}

// 改名后:utils.ts(先宽松)
function formatDate(d: any): string {
  return d.toISOString();
}

// 再逐步精确
interface DateLike {
  toISOString(): string;
}
function formatDate(d: DateLike): string {
  return d.toISOString();
}

第三步:为 JS 写声明

// legacy.js(不转的 JS 库)
// legacy.d.ts
declare module "./legacy" {
  export function oldFunction(a: number): string;
  export default class Legacy { run(): void }
}

// 简单粗暴方案:any
// declare module "./legacy";

第四步:开启 strict

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
npx tsc --noEmit   # 修复所有类型错误

迁移注意事项

  • 不要"一次性全转",按模块分批(MVP 优先)
  • 先转叶子模块(工具函数),后转入口
  • 无法确定的先 any,标记 TODO 逐步替换
  • 利用 @ts-ignore 临时跳过(注明原因)
  • 引入第三库时安装 @types

迁移工具

# 使用 codemod 工具辅助(如 ts-migrate)
npx @typescript/analyze-trace
# 或使用 IDE 的重命名/提取功能

# 检查迁移进度
npx tsc --noEmit --allowJs --checkJs | wc -l
💡 提示

迁移成功的标志:strict: true + 无类型错误。渐进式迁移比"推倒重写"风险小得多。

TypeScript 单元测试

为 TypeScript 项目编写单元测试:Jest/Vitest 的使用与类型安全。

测试工具选择

工具特点
VitestVite 生态,快,原生 TS
Jest最流行,生态完善
Node 内置 test零依赖,简单

安装 Vitest

npm install -D vitest
// package.json
{
  "scripts": {
    "test": "vitest"
  }
}

第一个测试

// src/utils/math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function divide(a: number, b: number): number {
  if (b === 0) throw new Error("不能除以 0");
  return a / b;
}
// src/utils/math.test.ts
import { describe, expect, it } from "vitest";
import { add, divide } from "./math";

describe("add", () => {
  it("两个正数相加", () => {
    expect(add(1, 2)).toBe(3);
  });

  it("负数相加", () => {
    expect(add(-1, -2)).toBe(-3);
  });
});

describe("divide", () => {
  it("正常除法", () => {
    expect(divide(10, 2)).toBe(5);
  });

  it("除以 0 抛错", () => {
    expect(() => divide(1, 0)).toThrow("不能除以 0");
  });
});

异步测试

// src/services/user.ts
export async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}
// user.test.ts
import { describe, expect, it, vi } from "vitest";
import { fetchUser } from "./user";

describe("fetchUser", () => {
  it("成功返回用户", async () => {
    // mock 全局 fetch
    vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({ id: 1, name: "张三" }),
    }));

    const user = await fetchUser(1);
    expect(user.name).toBe("张三");
  });

  it("请求失败抛错", async () => {
    vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
      ok: false,
      status: 500,
    }));

    await expect(fetchUser(1)).rejects.toThrow("HTTP 500");
  });
});

类型安全断言

// 类型层面的测试(编译期)
import { expectTypeOf } from "vitest";

expectTypeOf(add).parameter(0).toBeNumber();
expectTypeOf(add(1, 2)).toBeNumber();
// expectTypeOf(add("1", 2)).toBeNumber();  // 编译期报错 ✅
💡 提示

测试 + 类型检查双保险:tsc --noEmit 查编译期,Vitest 查运行时。CI 中两个都跑。

TypeScript infer 关键字

掌握 TypeScript infer 关键字:从类型中"提取"子类型。

infer 基础

// infer 只能在条件类型中使用
type Unwrap<T> = T extends Promise<infer U> ? U : T;

type A = Unwrap<Promise<string>>;   // string
type B = Unwrap<Promise<number[]>>; // number[]
type C = Unwrap<number>;            // number(非 Promise 原样)

提取数组元素

type ElementType<T> = T extends (infer E)[] ? E : never;

type N = ElementType<number[]>;    // number
type S = ElementType<string[]>;    // string
type O = ElementType<User[]>;      // User

提取函数类型

// 返回值
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;
type R = ReturnOf<() => string>;   // string

// 参数(元组)
type ParamsOf<T> = T extends (...args: infer P) => any ? P : never;
type P = ParamsOf<(a: number, b: string) => void>;  // [number, string]

// 第一个参数
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any
  ? F : never;
type F = FirstParam<(id: number, name: string) => void>;  // number

递归 infer

// 深层次解包 Promise
type DeepUnwrap<T> = T extends Promise<infer U>
  ? DeepUnwrap<U>                  // 递归
  : T;

type X = DeepUnwrap<Promise<Promise<Promise<number>>>>;  // number

// 提取元组最后一个元素
type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;
type L = Last<[1, 2, 3]>;   // 3

infer 实战应用

// 从 API 函数提取数据类型
const api = {
  getUsers: (): Promise<User[]> => fetch(...).then(r => r.json()),
  getUser: (id: number): Promise<User> => fetch(...).then(r => r.json()),
  saveUser: (u: User): Promise<void> => fetch(...),
};

type ApiReturn<K extends keyof typeof api> = Awaited<
  ReturnType<typeof api[K]>
>;

type Users = ApiReturn<"getUsers">;   // User[]
type  // User
💡 提示

infer 就像类型世界的"解构赋值":从 Promise/数组/函数/元组中提取出内部类型,是高级类型编程的基石。

TypeScript 索引类型

掌握 TypeScript 索引类型(Indexed Access Types):按"键"取类型。

基础索引访问

interface User {
  id: number;
  name: string;
  email: string;
}

// T[K]:取属性类型
type IdType = User["id"];      // number
type NameType = User["name"];  // string

// 取多个
type IDAndName = User["id" | "name"];  // number | string

// 取全部
type AllValues = User[keyof User];     // number | string

与 keyof 配合

// keyof:取所有键
type UserKeys = keyof User;   // "id" | "name" | "email"

// 通用取值函数
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user: User = { id: 1, name: "张三", email: "a@b.c" };
getValue(user, "id");     // number ✅
getValue(user, "name");   // string ✅
// getValue(user, "age")  // ❌ "age" 不存在

数组索引

// 数组元素类型
const users: User[] = [];
type Elem = typeof users[number];   // User

// 字面量数组(as const)
const methods = ["GET", "POST"] as const;
type Method = typeof methods[number];  // "GET" | "POST"

// 元组索引
type Tuple = [string, number, boolean];
type First = Tuple[0];    // string
type All = Tuple[number]; // string | number | boolean

嵌套索引

interface ApiResponse {
  data: {
    user: User;
    settings: { theme: string };
  };
}

type UserType = ApiResponse["data"]["user"];        // User
type ThemeType = ApiResponse["data"]["settings"]["theme"];  // string

索引访问实战

// 从配置对象生成类型
const config = {
  api: { baseUrl: "/api", timeout: 5000 },
  debug: true,
} as const;

type ConfigType = typeof config;
type ApiConfig = ConfigType["api"];
// { readonly baseUrl: "/api"; readonly timeout: 5000 }

// 枚举值类型
const ROLE = { Admin: "admin", User: "user" } as const;
type Role = typeof ROLE[keyof typeof ROLE];  // "admin" | "user"
💡 提示

索引访问(T[K])+ keyof + typeof 组合,能从现有值/类型推导出精确类型,减少重复定义。

TypeScript 递归类型

掌握 TypeScript 递归类型:定义自引用类型(树、链表、JSON)。

递归类型基础

// 递归:类型引用自身
interface TreeNode {
  value: number;
  children: TreeNode[];    // 子节点也是 TreeNode
}

const tree: TreeNode = {
  value: 1,
  children: [
    { value: 2, children: [] },
    {
      value: 3,
      children: [{ value: 4, children: [] }],
    },
  ],
};

递归类型别名

// 链表
type ListNode<T> = {
  value: T;
  next: ListNode<T> | null;   // 递归引用
};

const list: ListNode<number> = {
  value: 1,
  next: { value: 2, next: null },
};

// JSON 值(经典的递归类型)
type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

const json: JsonValue = {
  name: "CodeNest",
  version: 1,
  tags: ["教程", "编程"],
  meta: { author: "老王" },
};

递归映射类型

// 深层可选(递归映射)
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Settings {
  user: {
    profile: { name: string; bio: string };
    prefs: { theme: string };
  };
}

const partial: DeepPartial<Settings> = {
  user: { prefs: { theme: "dark" } },   // 任意深度可选 ✅
};

// 深层只读
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

递归条件类型

// 递归解包
type DeepUnwrap<T> = T extends Promise<infer U>
  ? DeepUnwrap<U>
  : T;

// 递归展开扁平化
type Flatten<T> = T extends (infer E)[]
  ? E extends any[]
    ? Flatten<E>
    : E
  : T;

type Flat = Flatten<number[][]>;       // number
type Flat2 = Flatten<string[][][]>;    // string

递归类型的注意点

  • 递归必须有终止条件(否则类型报错)
  • 接口递归(interface)比 type 别名更宽松
  • 递归层数过深可能导致编译变慢
  • 树/图/JSON/嵌套配置是典型场景
💡 提示

递归类型让"树形/嵌套数据"的类型定义与数据结构一致,是类型系统表达力的体现。

TypeScript 协变与逆变

理解 TypeScript 协变与逆变:类型兼容性的底层规则。

概念速览

概念含义示例
协变(Covariance)子类型可赋给父类型Dog[] 可赋给 Animal[]
逆变(Contravariance)参数方向相反函数参数可接受更宽的父类型
不变(Invariance)必须精确匹配读写都有的类型

协变示例

class Animal { name: string = "" }
class Dog extends Animal { bark(): void {} }

// 数组协变
const dogs: Dog[] = [new Dog()];
const animals: Animal[] = dogs;   // ✅ 子类型赋给父类型

// 返回值协变(安全)
type ReturnCovariant = () => Dog;
const f: ReturnCovariant = () => new Dog();
const g: () => Animal = f;        // ✅ 返回值可协变

逆变示例(函数参数)

class Animal {}
class Dog extends Animal {}

// 参数逆变:可接受更宽的父类型
type Handler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;

// ✅ 合法:DogHandler 可赋给 Handler
// (能处理所有 Animal 的函数,自然能处理 Dog)
const h: DogHandler = (dog: Dog) => {};
const h2: Handler = h;    // 参数逆变允许

// 实际中 TS 对方法参数默认"双变"(宽松)
interface A { method(x: Dog): void }
interface B { method(x: Animal): void }
// B 可赋给 A(方法参数双变)

strictFunctionTypes

// tsconfig 开启后,函数参数严格逆变
{
  "compilerOptions": {
    "strictFunctionTypes": true
  }
}

// 方法声明 vs 函数属性(区别!)
interface Foo {
  method(a: Dog): void;        // 方法:双变(宽松)
  fn: (a: Dog) => void;        // 函数属性:严格逆变
}

实践意义

// 事件处理器设计:用父类型参数更灵活
interface EventBus {
  on(event: string, handler: (data: unknown) => void): void;
}

// 子类型处理器
const bus: EventBus = {
  on(event, handler) {
    // 内部调用
    handler({ type: event });
  },
};

bus.on("user.created", (data: unknown) => {
  // 使用时再收窄
  if (typeof data === "object" && data !== null && "type" in data) {
    console.log(data.type);
  }
});
💡 提示

协变/逆变是"为什么这样赋值合法/非法"的底层解释;日常开发记住结论即可:返回值协变、参数逆变。

TypeScript 路径映射

掌握 TypeScript 路径映射:用别名替代长相对路径。

问题场景

// 深层相对路径难维护
import { format } from "../../../utils/format";
import { User } from "../../../types/user";

// 重构移动文件后路径全乱

配置路径别名

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"],
      "@types/*": ["src/types/*"]
    }
  }
}
// 使用别名
import { format } from "@utils/format";
import { User } from "@types/user";
import { Button } from "@components/Button";

Vite 中配置

// vite.config.ts
import { defineConfig } from "vite";
import { fileURLToPath } from "node:url";

export default defineConfig({
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
      "@components": fileURLToPath(new URL("./src/components", import.meta.url)),
    },
  },
});

Node/tsx 中配置

// tsconfig.json 加:
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["./src/*"] }
  }
}

// 运行工具:
// tsx(支持 tsconfig paths)
// 或 tsconfig-paths
// npx tsx -r tsconfig-paths/register src/index.ts

别名命名规范

前缀目录
@/src 根
@components/组件
@utils/工具
@hooks/Hooks
@types/类型

别名的好处

  • 路径短、语义清晰
  • 移动文件不用改引用
  • IDE 自动补全支持
💡 提示

改 tsconfig 后 IDE 可能提示"路径不识别",重启 TS server 或执行 npx tsc --noEmit 验证。

TypeScript 项目引用

掌握 TypeScript 项目引用(Project References):大型项目的编译组织方式。

为什么需要项目引用

  • 大型项目拆分编译,加快构建
  • 模块间类型依赖管理
  • 一次编译,多处复用(.d.ts)

项目结构

monorepo/
├── packages/
│   ├── shared/          # 公共代码
│   │   ├── src/
│   │   └── tsconfig.json
│   ├── server/          # 服务端
│   │   ├── src/
│   │   └── tsconfig.json
│   └── web/             # 前端
│       ├── src/
│       └── tsconfig.json
└── tsconfig.base.json   # 公共配置

配置引用

// tsconfig.base.json
{
  "compilerOptions": {
    "composite": true,          // 必须开启
    "declaration": true,
    "declarationMap": true,
    "target": "ES2020",
    "strict": true
  }
}
// packages/shared/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"]
}
// packages/server/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "paths": { "@shared/*": ["../shared/src/*"] }
  },
  "references": [
    { "path": "../shared" }     // 引用 shared
  ],
  "include": ["src"]
}

构建模式

# 构建所有引用(--build)
tsc --build

# 增量构建
tsc --build --force

# 只构建单个项目
tsc --build packages/server

# 监听模式
tsc --build --watch

引用带来的好处

  • 只编译有变动的项目(增量)
  • 跨项目类型共享(通过 .d.ts)
  • 依赖顺序自动处理

注意事项

  • 被引用项目必须 composite: true
  • 用 tsc --build 而非 tsc
  • IDE 中可能要重启 TS server
💡 提示

简单项目用 references + tsc --build 即可;超大型 monorepo 可考虑 Turborepo/Nx 等构建工具。

TypeScript Monorepo 配置

掌握 TypeScript Monorepo 配置:多包仓库的类型与构建管理。

什么是 Monorepo

多个项目(包)放在一个仓库中管理:

my-company/
├── packages/
│   ├── ui/            # 组件库
│   ├── utils/         # 工具库
│   ├── api/           # 接口层
│   ├── web/           # 前端应用
│   └── server/        # 后端应用
├── package.json       # 根(workspaces)
├── tsconfig.base.json
└── pnpm-workspace.yaml  # 或 package.json workspaces

配置 workspace

// 根 package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*"],
  "scripts": {
    "build": "tsc --build",
    "dev": "concurrently -n ui,web,server -c blue,green,red \"npm run dev -w packages/ui\" \"npm run dev -w packages/web\""
  }
}

每个包的类型配置

// packages/ui/package.json
{
  "name": "@company/ui",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsc --build",
    "dev": "tsc --build --watch"
  }
}
// packages/web/package.json
{
  "name": "@company/web",
  "dependencies": {
    "@company/ui": "workspace:*",     // workspace 依赖
    "@company/utils": "workspace:*"
  },
  "scripts": {
    "dev": "vite"
  }
}

共享类型基础配置

// tsconfig.base.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler"
  }
}

开发与构建命令

# 安装全部依赖
pnpm install

# 构建所有包(按依赖顺序)
pnpm build

# 只构建某个包
pnpm --filter @company/ui build

# 运行 web 开发
pnpm --filter @company/web dev

Monorepo 工具选择

工具特点
pnpm workspaces快、磁盘省(推荐)
npm workspaces内置,简单
Turborepo构建缓存,任务编排
Nx大型企业级
💡 提示

Monorepo 的核心收益:代码共享 + 统一管理 + 原子化提交。小团队 3-5 个包用 pnpm workspaces + tsc --build 足够。

TypeScript 设计模式

掌握 TypeScript 中的设计模式:用类型系统实现经典模式。

1. 单例模式

class ConfigManager {
  private static instance: ConfigManager;
  private config: Record<string, string> = {};

  private constructor() {}   // 私有构造器

  static getInstance(): ConfigManager {
    if (!ConfigManager.instance) {
      ConfigManager.instance = new ConfigManager();
    }
    return ConfigManager.instance;
  }

  set(key: string, value: string): void {
    this.config[key] = value;
  }

  get(key: string): string | undefined {
    return this.config[key];
  }
}

const a = ConfigManager.getInstance();
const b = ConfigManager.getInstance();
a === b;   // true(同一实例)

2. 工厂模式

interface Payment {
  pay(amount: number): void;
}

class WechatPay implements Payment {
  pay(amount: number): void {
    console.log(`微信支付 ${amount} 元`);
  }
}

class Alipay implements Payment {
  pay(amount: number): void {
    console.log(`支付宝支付 ${amount} 元`);
  }
}

type PaymentType = "wechat" | "alipay";

class PaymentFactory {
  static create(type: PaymentType): Payment {
    switch (type) {
      case "wechat": return new WechatPay();
      case "alipay": return new Alipay();
    }
  }
}

const payment = PaymentFactory.create("wechat");
payment.pay(100);

3. 观察者模式

type Listener<T> = (data: T) => void;

class EventEmitter<T> {
  private listeners: Listener<T>[] = [];

  on(listener: Listener<T>): void {
    this.listeners.push(listener);
  }

  off(listener: Listener<T>): void {
    this.listeners = this.listeners.filter(l => l !== listener);
  }

  emit(data: T): void {
    this.listeners.forEach(l => l(data));
  }
}

// 类型安全的事件
const userEvents = new EventEmitter<{ id: number; name: string }>();

userEvents.on((user) => console.log(`用户 ${user.name} 登录`));
userEvents.emit({ id: 1, name: "张三" });

4. 策略模式

interface SortStrategy<T> {
  sort(list: T[]): T[];
}

class QuickSort implements SortStrategy<number> {
  sort(list: number[]): number[] { /* ... */ return list; }
}

class BubbleSort implements SortStrategy<number> {
  sort(list: number[]): number[] { /* ... */ return list; }
}

class Sorter {
  constructor(private strategy: SortStrategy<number>) {}

  setStrategy(strategy: SortStrategy<number>): void {
    this.strategy = strategy;
  }

  sort(data: number[]): number[] {
    return this.strategy.sort(data);
  }
}

const sorter = new Sorter(new QuickSort());
sorter.sort([3, 1, 2]);
sorter.setStrategy(new BubbleSort());   // 切换策略

5. 依赖注入(简化)

interface Logger {
  log(msg: string): void;
}

class ConsoleLogger implements Logger {
  log(msg: string): void { console.log(msg); }
}

class UserService {
  constructor(private logger: Logger) {}   // 依赖注入

  createUser(name: string): void {
    this.logger.log(`创建用户 ${name}`);
  }
}

// 组合与测试
const service = new UserService(new ConsoleLogger());
// 测试时注入 mock
const mockLogger: Logger = { log: () => {} };
💡 提示

TS 的类型系统让设计模式更安全:接口约束契约、泛型保证复用、枚举/联合限定选项。先理解模式思想,再套用 TS 写法。

TypeScript 性能优化

掌握 TypeScript 性能优化:编译提速、类型层面优化与运行时注意点。

1. 编译性能优化

// tsconfig.json
{
  "compilerOptions": {
    "skipLibCheck": true,          // 跳过 .d.ts 检查(大幅提速)
    "incremental": true,           // 增量编译
    "tsBuildInfoFile": "./.cache/tsconfig.tsbuildinfo",
    "noEmit": true                 // 配合 esbuild/vite 转译时
  }
}

// 或使用 esbuild 转译(快 10-100 倍)
// Vite 默认用 esbuild,类型检查由 IDE 完成

2. 避免类型爆炸

// ❌ 深层递归类型
type Bad = Bad[];

// ❌ 巨大的联合类型(数千成员)
type Huge = "a0" | "a1" | ...;

// ❌ 过度泛型嵌套
function f<T extends Record<string, any>>(x: T): T[keyof T] { ... }

// ✅ 合理拆分、适度抽象

3. 类型层面优化

// 用接口而非联合(IDE 更快)
interface User { id: number; name: string }   // ✅
// type User = { id: number } & { name: string }  // 慢一点

// 避免字符串拼接类型
type A = `${string}-${string}-${string}`;   // 谨慎使用

// 用现有类型推导而非重复定义
type UserId = User["id"];    // ✅ 而不是再写一遍 number

4. 运行时性能注意

// 类型是编译期的,运行时无开销(大部分)
// 但有运行时成本的部分:

// 1. 枚举(编译成对象)
enum Color { Red, Green }    // 有运行时对象
// 替代:联合类型(无运行时开销)
type Color = "red" | "green";

// 2. 装饰器(有运行时函数)
// 3. 参数属性(构造器赋值)
// 4. 命名空间(编译成对象)

5. 工程层面优化

  • 用 tsc --build 增量构建(项目引用)
  • 类型检查与转译分离(Vite/esbuild 转译 + tsc 检查)
  • CI 中只 tsc --noEmit 检查,不重复编译
  • 用 moduleResolution: "bundler" 减少解析开销

6. 排查工具

# 查看编译耗时
npx tsc --noEmit --extendedDiagnostics

# 查看每个文件耗时
npx tsc --noEmit --generateTrace ./.cache/trace
npx @typescript/analyze-trace ./.cache/trace
💡 提示

现代实践:esbuild 转译(快)+ tsc --noEmit 检查(准) 分离,兼顾速度与类型安全。

TypeScript 综合项目

综合运用 TypeScript 知识,完成一个综合项目:类型安全的任务管理 API。

项目规划

功能:
1. 任务 CRUD(增删改查)
2. 状态管理(待办/进行中/完成)
3. 优先级
4. 标签
5. 类型安全的数据层

类型定义

// types.ts
export type TaskStatus = "todo" | "doing" | "done";
export type Priority = "low" | "medium" | "high";

export interface Task {
  id: number;
  title: string;
  description?: string;
  status: TaskStatus;
  priority: Priority;
  tags: string[];
  createdAt: Date;
  updatedAt: Date;
}

export type CreateTaskInput = Omit<Task, "id" | "createdAt" | "updatedAt">;
export type UpdateTaskInput = Partial<CreateTaskInput>;
export type SafeTask = Omit<Task, "description">;

数据存储

// store.ts
import { Task, CreateTaskInput } from "./types";

class TaskStore {
  private tasks: Map<number, Task> = new Map();
  private nextId = 1;

  create(input: CreateTaskInput): Task {
    const now = new Date();
    const task: Task = {
      id: this.nextId++,
      ...input,
      createdAt: now,
      updatedAt: now,
    };
    this.tasks.set(task.id, task);
    return task;
  }

  getAll(): Task[] {
    return [...this.tasks.values()].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
  }

  getById(id: number): Task | undefined {
    return this.tasks.get(id);
  }

  update(id: number, input: Partial<CreateTaskInput>): Task | undefined {
    const task = this.tasks.get(id);
    if (!task) return undefined;
    const updated = { ...task, ...input, updatedAt: new Date() };
    this.tasks.set(id, updated);
    return updated;
  }

  delete(id: number): boolean {
    return this.tasks.delete(id);
  }

  filterByStatus(status: Task["status"]): Task[] {
    return this.getAll().filter(t => t.status === status);
  }
}

export const store = new TaskStore();

服务层

// service.ts
import { store } from "./store";
import { CreateTaskInput, Task } from "./types";

export interface TaskResult<T> {
  success: boolean;
  data?: T;
  error?: string;
}

export class TaskService {
  createTask(input: CreateTaskInput): TaskResult<Task> {
    if (!input.title.trim()) {
      return { success: false, error: "标题不能为空" };
    }
    return { success: true, data: store.create(input) };
  }

  listTasks(status?: Task["status"]): Task[] {
    return status ? store.filterByStatus(status) : store.getAll();
  }

  completeTask(id: number): TaskResult<Task> {
    const task = store.update(id, { status: "done" });
    if (!task) return { success: false, error: "任务不存在" };
    return { success: true, data: task };
  }
}

测试

// service.test.ts
import { describe, expect, it, beforeEach } from "vitest";
import { TaskService } from "./service";
import { store } from "./store";

describe("TaskService", () => {
  let service: TaskService;

  beforeEach(() => {
    service = new TaskService();
  });

  it("创建任务", () => {
    const result = service.createTask({
      title: "学习 TypeScript",
      status: "todo",
      priority: "high",
      tags: ["学习"],
    });
    expect(result.success).toBe(true);
    expect(result.data?.title).toBe("学习 TypeScript");
  });

  it("空标题报错", () => {
    const result = service.createTask({
      title: "",
      status: "todo",
      priority: "low",
      tags: [],
    });
    expect(result.success).toBe(false);
  });
});
💡 提示

综合项目要点:类型定义先行(types.ts)、分层清晰(store/service)、类型安全贯穿(泛型、Omit、Partial)。

TypeScript + Node.js 实战

TypeScript + Node.js 实战:用 TS 构建后端服务。

项目初始化

mkdir ts-node-api && cd ts-node-api
npm init -y
npm install express
npm install -D typescript @types/node @types/express tsx
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true
  },
  "include": ["src"]
}

类型化路由

// src/server.ts
import express, { Request, Response, NextFunction } from "express";

const app = express();
app.use(express.json());

interface User {
  id: number;
  name: string;
  email: string;
}

// 内存数据
const users: User[] = [{ id: 1, name: "张三", email: "z@example.com" }];
let nextId = 2;

// 类型化的请求处理
app.get("/api/users", (_req: Request, res: Response) => {
  res.json(users);
});

app.get("/api/users/:id", (req: Request, res: Response) => {
  const id = Number(req.params.id);
  const user = users.find(u => u.id === id);
  if (!user) {
    res.status(404).json({ error: "用户不存在" });
    return;
  }
  res.json(user);
});

app.post("/api/users", (req: Request, res: Response) => {
  const { name, email } = req.body as Partial<User>;
  if (!name || !email) {
    res.status(400).json({ error: "缺少字段" });
    return;
  }
  const user: User = { id: nextId++, name, email };
  users.push(user);
  res.status(201).json(user);
});

// 错误处理中间件
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
  console.error(err);
  res.status(500).json({ error: "服务器错误" });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server on :${PORT}`));

运行脚本

// package.json
{
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js"
  }
}

与环境变量类型化

// env.ts
const getEnv = (key: string): string => {
  const value = process.env[key];
  if (!value) throw new Error(`缺少环境变量 ${key}`);
  return value;
};

export const env = {
  port: Number(getEnv("PORT") ?? 3000),
  dbUrl: getEnv("DB_URL"),
  secret: getEnv("JWT_SECRET"),
};

Node 内置 API 类型

// 文件操作(类型安全)
import { readFile, writeFile } from "node:fs/promises";

async function loadTasks(): Promise<Task[]> {
  const raw = await readFile("data.json", "utf-8");
  return JSON.parse(raw) as Task[];
}

// 路径
import { join } from "node:path";
const file = join(process.cwd(), "data", "tasks.json");
💡 提示

Node + TS 现代实践:tsx 开发(免编译)、tsc 构建(类型检查)、Express/Fastify + 类型化路由。

TypeScript + Vue3 实战

TypeScript + Vue 3 实战:Vue 3 的 TS 支持(组合式 API + 类型化 Props)。

创建项目

npm create vite@latest vue-ts-app -- --template vue-ts
cd vue-ts-app
npm install

类型化 Props

<script setup lang="ts">
// 定义 Props 类型
interface Props {
  title: string;
  count?: number;          // 可选
  items: string[];
  onSelect?: (item: string) => void;   // 事件
}

// 类型安全地接收
const props = withDefaults(defineProps<Props>(), {
  count: 0,
});

// 事件类型
const emit = defineEmits<{
  (e: "update", value: string): void;
  (e: "delete", id: number): void;
}>();
</script>

<template>
  <div>
    <h2>{{ props.title }}</h2>
    <p>数量:{{ props.count }}</p>
  </div>
</template>

类型化 ref 与 reactive

<script setup lang="ts">
import { ref, reactive, computed } from "vue";

interface Task {
  id: number;
  title: string;
  done: boolean;
}

// ref 泛型
const count = ref<number>(0);
const tasks = ref<Task[]>([]);

// reactive
const form = reactive({
  title: "",
  priority: "medium" as "low" | "medium" | "high",
});

// computed 自动推断
const doneCount = computed(() => tasks.value.filter(t => t.done).length);

async function loadTasks() {
  const res = await fetch("/api/tasks");
  tasks.value = await res.json() as Task[];
}
</script>

类型化 API 层

// src/api/tasks.ts
export interface Task {
  id: number;
  title: string;
  done: boolean;
}

export async function fetchTasks(): Promise<Task[]> {
  const res = await fetch("/api/tasks");
  if (!res.ok) throw new Error("加载失败");
  return res.json();
}

export async function createTask(title: string): Promise<Task> {
  const res = await fetch("/api/tasks", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title }),
  });
  return res.json();
}

类型安全模板

<template>
  <ul>
    <li v-for="task in tasks" :key="task.id">
      <span :class="{ done: task.done }">{{ task.title }}</span>
      <button @click="toggle(task)">切换</button>
    </li>
  </ul>
</template>
💡 提示

Vue 3 全家桶 TS 支持完善:defineProps<T>() 类型化 Props、ref/reactive 泛型、API 层返回 Promise<T>,全程类型安全。

TypeScript + React 实战

TypeScript + React 实战:React 生态的完整 TS 实践。

创建项目(TS 模板)

npm create vite@latest react-ts-app -- --template react-ts
cd react-ts-app
npm install

类型化 Props

// src/components/UserCard.tsx
interface UserCardProps {
  user: User;
  onSelect?: (id: number) => void;
  variant?: "default" | "compact";   // 联合类型
}

function UserCard({ user, onSelect, variant = "default" }: UserCardProps) {
  return (
    <div className={`card card-${variant}`} => onSelect?.(user.id)}>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

export default UserCard;

类型化状态

// src/types.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export type LoadState = "idle" | "loading" | "success" | "error";

// src/components/UserList.tsx
import { useState, useEffect } from "react";
import type { User, LoadState } from "../types";

function UserList() {
  const [users, setUsers] = useState<User[]>([]);
  const [state, setState] = useState<LoadState>("idle");

  useEffect(() => {
    setState("loading");
    fetch("/api/users")
      .then(r => r.json())
      .then((data: User[]) => {
        setUsers(data);
        setState("success");
      })
      .catch(() => setState("error"));
  }, []);

  if (state === "loading") return <p>加载中...</p>;
  if (state === "error") return <p>出错了</p>;

  return (
    <div>
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

类型化 Hooks

// src/hooks/useLocalStorage.ts
import { useState, useEffect } from "react";

function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) as T : initial;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;   // 元组类型
}

// 使用
const [theme, setTheme] = useLocalStorage<"light" | "dark">("theme", "light");

类型化事件处理

import { FormEvent, ChangeEvent } from "react";

function LoginForm() {
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // ...
  };

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  return (
    <form
      <input />
    </form>
  );
}

类型化 Context

// src/store/AuthContext.tsx
import { createContext, useContext, ReactNode } from "react";

interface AuthContextValue {
  user: User | null;
  login: (token: string) => void;
  logout: () => void;
}

const AuthContext = createContext<AuthContextValue | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  // ... 实现
}

export function useAuth(): AuthContextValue {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth 必须在 AuthProvider 内使用");
  return ctx;
}
💡 提示

React + TS 黄金实践:Props/State 全部类型化、API 层返回 Promise<T>、自定义 Hook 用泛型、Context 提供类型安全的 hook。

第 1 / 72 节