TypeScript 类型系统与全栈项目教程
从"图纸与施工"到"前后端签合同":系统学类型系统,并用共享类型打通全栈(TypeScript 5.x)
目录
- 00 · TS 是什么(图纸与施工)
- 01 · 环境搭建与编译流程
- 02 · 基础类型系统
- 03 · 函数类型
- 04 · 接口与类型别名
- 05 · 类与面向对象
- 06 · 泛型深入
- 07 · 类型体操与工具类型
- 08 · 项目工程化
- 09 · 前后端共享类型
- 10 · 实战:全栈 Todo
- 11 · 踩坑速查
- 12 · API 速查
- 13 · 术语表
00 · TS 是什么(图纸与施工)
🏗 生活类比:两种盖楼方式
JavaScript(边盖边改):施工队不画图纸直接盖。盖到一半发现"这里要放电梯但承重墙已经砌好了"——运行时才出错,改起来伤筋动骨。
TypeScript(图纸先行):动工前把每一面墙、每一根管线都画进图纸(类型),监理按图验收(编译检查)——开工前就能发现承重墙和电梯的矛盾,改图纸的成本比拆墙低一百倍。
TypeScript = JavaScript + 类型系统:写的是带类型的 JS,编译后"擦掉类型"变回纯 JS 运行——类型只活在开发期,运行时不产生任何额外成本。
核心心智模型
- 类型(Type)= 图纸上的规格说明:这个变量装什么、这个函数吃什么吐什么
- 编译器(tsc)= 图纸审查员:开工前检查所有规格是否自洽
- 类型擦除= 图纸只留在设计阶段,施工(运行)时用不到图纸
- 运行时不检查:类型错误 100% 在编译期暴露,不增加运行时开销
| 关键指标 | 数值 | 说明 |
|---|---|---|
| 编译期可拦截 bug | ~70% | 微软实测数据 |
| 运行时额外开销 | 0 | 类型被擦除 |
| IDE 效率提升 | 30%+ | 类型驱动补全/重构 |
01 · 环境搭建与编译流程
🏗 生活类比:设计院的工作流
图纸(.ts)→ 设计院审核(tsc编译检查)→ 出施工图(生成.js)→ 施工队按图施工(node 运行)。审核不通过就不出施工图——这就是"编译失败就不产生产物"。
最小工程
# 初始化项目并安装 TypeScriptnpminit-ynpminstall-Dtypescript @types/node# 生成 tsconfig.json 并打开严格模式npx tsc--init--strict# 编译(src 下的 .ts → dist 下的 .js) 与 监听模式npx tsc npx tsc--watchtsconfig.json(核心配置)
{"compilerOptions":{"target":"ES2022","module":"commonjs","rootDir":"./src","outDir":"./dist","strict":true,"esModuleInterop":true,"skipLibCheck":true},"include":["src"]}💡strict 全家桶(建议永远开着):
noImplicitAny(禁止隐式 any)、strictNullChecks(null/undefined 必须显式处理)、noUncheckedIndexedAccess(数组索引可能 undefined)——这三项拦住大多数"偷偷用 any"和"空值爆炸"。
02 · 基础类型系统
🏗 生活类比:建材仓库
基础类型 = 标准建材:钢材(number)、木板(string)、开关(boolean)——每种建材有明确的规格。元组(tuple)= 固定尺寸的预制件([宽, 高]);枚举(enum)= 编号清单;联合类型(union)= “这面墙要么是砖要么是玻璃”;unknown= “来料未知,先质检(收窄)再用”。
基础类型全览
constname:string="客厅";constarea:number=42.5;constrented:boolean=false;consttags:string[]=["南向","阳光房"];/* 元组: 固定长度与类型 */constdim:[number,number]=[4,3];// [宽, 高]/* 枚举: 有名字的数字/字符串常量 */enumRoomType{Living,Bedroom,Kitchen}/* 字面量类型 + 联合类型: 值本身当类型 */typeDirection="north"|"south"|"east"|"west";letfacing:Direction="north";// ✅// facing = "up"; // ❌ "up" 不在联合里/* unknown vs any: 未知值必须先收窄 */functionparseRoom(raw:unknown):string{if(typeofraw==="string")returnraw.trim();// 收窄后安全thrownewError("bad input");}// let x: any = 1; x.foo(); // ❌ any: 类型检查完全放行, 等于没图纸/* never: 不可能的值(穷尽检查) */functionassertNever(v:never):never{thrownewError("unreachable");}| 类型 | 含义 | 典型用途 |
|---|---|---|
| number / string / boolean | 基础值 | 一切 |
| 数组 T[] / 元组 [T, U] | 列表 / 定长组 | 列表数据 / 坐标对 |
| enum | 命名常量 | 状态码、类型编号 |
| 字面量 + 联合 | 值域受限 | 选项参数、判别联合 |
| unknown | 未知(安全) | 外部输入、JSON 解析 |
| any | 放弃检查(危险) | 尽量不用;迁移期过渡 |
| never | 不可能 | 穷尽检查、抛错函数 |
| void / null / undefined | 无返回值 / 空 | 副作用函数 |
⚠any 是"没画图纸":
any让该处类型检查完全失效——就像这面墙没画进图纸,监理不验收。unknown 才是安全版 any:必须收窄(typeof / instanceof / 自定义守卫)后才能用。
03 · 函数类型
🏗 生活类比:标准化插座
一台机器(函数)的输入插头(参数)和输出接口(返回值)都有国家标准的形状——接错了插不进,插进了就能通电。泛型函数= 万能适配器:不管插头是两孔还是三孔,适配器把类型"透传"过去。
函数类型:声明与用法
/* 参数与返回类型 */functioncalcPrice(area:number,unit:number=100):number{returnarea*unit;}constp1=calcPrice(42.5);// 默认参数: 4250/* 可选参数 */functiondescribe(name:string,floor?:number):string{returnfloor?name+" @ "+floor:name;}/* 函数类型标注(变量/参数/回调) */typeMapper=(x:number)=>number;constdouble:Mapper=(x)=>x*2;functionapply(arr:number[],fn:Mapper):number[]{returnarr.map(fn);}/* 重载: 同一函数多组签名(按参数形状分发) */functionbuild(w:number,h:number):[number,number];functionbuild(size:number):[number,number];functionbuild(a:number,b?:number):[number,number]{returnb===undefined?[a,a]:[a,b];}泛型函数:类型透传
<