1. Express 的类型是怎么设计的
@types/express故意留了一个「可扩展的口子」。
在@types/express-serve-static-core里大致是这样:
declareglobal{namespaceExpress{// 空接口,专门给你扩展用的interfaceRequest{}interfaceResponse{}}}exportinterfaceRequest<...>extendshttp.IncomingMessage,Express.Request{// get、params、body 等都在这里}关键关系:
你扩展的 Express.Request(全局) ↑ 继承 express 导出的 Request所以:不是直接改import { Request } from "express"的定义,而是扩展全局的Express.Request。扩展成功后,所有extends Express.Request的地方都会自动带上你的字段。
2. 声明合并:TypeScript 的核心机制
TypeScript 允许同名interface自动合并:
interfaceUser{name:string;}interfaceUser{age:number;}// 等价于:// interface User { name: string; age: number; }namespace+interface也支持合并:
namespaceExpress{interfaceRequest{userId?:number;}}// 会和 @types/express 里已有的 Express.Request 合并记住一条规则:
interface可以合并,type别名不行。
3. 为什么需要declare global
你的增强写在自己的文件里,不是在@types/express包内部。
文件有两种身份:
| 身份 | 判断条件 | 顶层声明落在哪 |
|---|---|---|
| 脚本(script) | 没有import/export | 直接进全局 |
| 模块(module) | 有import或export | 只在模块内,不进全局 |
Express命名空间定义在全局里。你的.d.ts如果是模块,里面的namespace Express默认只是模块局部的,合并不了全局那个。
下面两种写法二选一,不要两种都写。
模块写法(推荐)— 文件里会有import/export(例如配合export {})时使用:
declareglobal{namespaceExpress{interfaceRequest{userId?:number;}}}含义:「虽然这个文件是模块,但请把里面的声明放进全局作用域。」
脚本写法— 整个.d.ts没有任何import/export时可直接写:
namespaceExpress{interfaceRequest{userId?:number;}}怎么选
这个 .d.ts 里会不会出现 import? ├── 不会 → 直接写 namespace Express { ... } 就行 └── 会 / 不确定 → 用 declare global + export {}4. 为什么需要export {}
declare global只能在模块里用。
你的文件如果只有declare global { ... },没有import/export,TypeScript 会把它当脚本,declare global要么报错,要么不生效。
export{};作用:把文件标记成模块,且:
- 不导出任何实际值
.d.ts编译后不会产生 JS- 这是社区里最常用的「零副作用模块标记」
等价写法还有:
import"express";// 也可以,但会多一层对 express 的依赖引用exporttype{};// 也可以口诀:用了
declare global,文件末尾就要有import或export。
5. 你的文件逐块理解(完整心智模型)
declareglobal{// ① 在模块里,把下面内容注入全局namespaceExpress{// ② 找到全局 Express 命名空间interfaceRequest{// ③ 与已有 Request 合并(不是覆盖)userId?:number;// ④ 新增可选字段}}}export{};// ⑤ 让本文件成为模块,① 才合法五句话对应五个概念:全局注入 → 命名空间 → 接口合并 → 字段定义 → 模块标记。
6.tsconfig.json的include要覆盖到它
类型声明文件要被 TypeScript 读到,include需覆盖到.d.ts所在目录。例如本项目的配置:
{"compilerOptions":{xxx},"include":["**/*.ts"],"exclude":["node_modules"]}其中"include": ["**/*.ts"]会匹配**/*.d.ts,因此src/types/express.d.ts会被纳入编译。