这两行代码的核心区别在于:它们引入的是“类型”还是“运行时的值”。
在 TypeScript 和 NestJS(通常配合 ts-node 或构建工具如 swc/babel)的开发场景中,这个区别至关重要。
1. 核心区别总结
| 特性 | import type { ... } | import { ... } |
|---|---|---|
| 引入内容 | 仅引入 TypeScript 类型定义 | 引入 运行时的实际值 (类、函数、对象等) |
| 编译后结果 | 完全被移除。生成的 .js 文件中没有这行代码。 | 保留。生成的 .js 文件中会有 require('express') 或 import 语句。 |
| 运行时影响 | 无。不会增加打包体积,不会触发模块加载。 | 有。会加载 express 模块,增加启动时间和打包体积。 |
| 主要用途 | 用于变量类型注解、函数参数/返回值类型。 | 用于实际调用类构造函数、使用常量、调用工具函数。 |
| NestJS 装饰器场景 | 推荐/必须 (当仅用于类型注解时) | 错误/多余 (如果只用来做类型注解) |
2. 详细场景分析
场景 A:仅用于类型注解(最常见)
在 NestJS 控制器中,我们通常只需要 Request 和 Response 来告诉 TypeScript 参数的类型是什么,而不需要在代码逻辑中去 new Request() 或者调用 Response 的静态方法。
// ✅ 正确做法:使用 import type
import type { Request, Response } from 'express';
import { Controller, Get, Req, Res } from '@nestjs/common';
@Controller('example')
export class ExampleController {
// 这里的 Request 和 Response 仅在编译阶段存在,用于检查类型
@Get()
handle(
@Req() req: Request,
@Res() res: Response
): string {
// 运行时,req 是由 NestJS 注入的 express 请求实例
// 这里的代码不需要 'express' 模块被加载
return req.url;
}
}
编译后的 JavaScript (example.js):
// 'express' 的导入完全消失了!
const common_1 = require("@nestjs/common");
let ExampleController = class ExampleController {
handle(req, res) {
return req.url;
}
};
// ...
- 优点:减小了 bundle 体积,加快了应用启动速度(因为不需要去 node_modules 里加载 express 模块来做这件事)。
场景 B:需要运行时值
如果你需要在代码中实际使用 express 导出的某个类或函数(例如手动创建实例,虽然这在 NestJS 控制器中很少见),则必须使用普通导入。
// ❌ 如果这里用 import type,下面这行会报错:"'Request' refers to a value, but is being used as a value here."
import { Request } from 'express';
// 假设 express 导出了一个工具函数或你需要 new 一个对象(极少在 Controller 中这么做)
// const myReq = new Request(...);
注意:express 包中的 Request 和 Response 实际上是 接口 (Interfaces) 而不是类 (Classes)。
- 接口在 JavaScript 运行时是不存在的。
- 因此,你永远无法在运行时
new Request()或访问Request.someStaticMethod。 - 结论:对于
express的Request和Response,永远应该使用import type。如果你使用了import { Request },虽然代码能跑(因为 TypeScript 编译器有时会宽容处理,或者 Babel 配置问题),但这引入了不必要的运行时依赖,且语义上是错误的。
3. 为什么 NestJS 文档或旧代码有时混用?
- 历史原因:在较旧的 TypeScript 版本(3.8 之前)中,没有
import type语法,开发者被迫使用import { ... },然后依靠编译器配置isolatedModules或 babel 插件来剔除类型。 - 惯性:很多老项目或教程没有更新写法。
- Babel/SWC 配置:某些构建配置会自动将仅用于类型的
import { ... }转换为import type,但这依赖于工具链配置,不如显式写出import type安全及明确。
4. 最佳实践建议
在 NestJS (以及任何现代 TypeScript 项目) 中:
- 默认规则:如果你引入的东西只出现在冒号
:后面(类型注解)、<T>泛型中、或implements后面,请始终使用import type。 - 针对 Express/Fastify:由于
Request和Response纯粹是接口,必须使用:import type { Request, Response } from 'express'; // 或者 import type { FastifyRequest, FastifyReply } from 'fastify';
这样做可以确保你的代码在任何构建配置下都是最优的,并且清晰地表达了意图:“我只需要这个类型,不需要加载这个模块”。