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,看清两者差异与取舍。
核心差异
| 对比项 | JavaScript | TypeScript |
|---|---|---|
| 类型系统 | 动态(运行时) | 静态(编译期) |
| 类型错误 | 运行时才发现 | 编译期报错 |
| 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 # 列出参与编译的文件
编译选项分类
| 类别 | 选项 | 说明 |
|---|---|---|
| 目标 | target | JS 版本 |
| 模块 | module | 模块格式 |
| 严格性 | strict | 严格模式总开关 |
| 严格性 | noImplicitAny | 禁止隐式 any |
| 严格性 | strictNullChecks | 空值检查 |
| 输出 | outDir | 输出目录 |
| 输出 | declaration | 生成 .d.ts |
| 检查 | noUnusedLocals | 未使用局部变量 |
| 检查 | noFallthroughCasesInSwitch | switch 穿透检查 |
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 对象
| 对比项 | Map | Object |
|---|---|---|
| 键类型 | 任意(含对象/函数) | 字符串/Symbol |
| 顺序 | 插入顺序 | 不保证 |
| size | map.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 联合类型
| 对比项 | Enum | Union(联合) |
|---|---|---|
| 运行时存在 | ✅ 有对象 | ❌ 纯编译期 |
| 反向映射 | ✅ | ❌ |
| 类型安全 | 严格 | 严格 |
| 推荐度 | 传统选择 | 现代推荐(更简单) |
现代替代: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/Set | WeakMap/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 别名
| 对比项 | interface | type |
|---|---|---|
| 扩展 | 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
| 对比项 | type | interface |
|---|---|---|
| 联合/交叉 | ✅ 原生支持 | ❌ 需借助 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:渐进式迁移策略。
迁移策略概览
- 先搭 TS 环境,保持 JS 可运行
- 把
.js改成.ts,用宽松配置 - 逐步收紧类型(any → 具体类型)
- 开启 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 的使用与类型安全。
测试工具选择
| 工具 | 特点 |
|---|---|
| Vitest | Vite 生态,快,原生 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。