import type 和 import 区别

9 阅读3分钟

这两行代码的核心区别在于:它们引入的是“类型”还是“运行时的值”

在 TypeScript 和 NestJS(通常配合 ts-node 或构建工具如 swc/babel)的开发场景中,这个区别至关重要。

1. 核心区别总结

特性import type { ... }import { ... }
引入内容仅引入 TypeScript 类型定义引入 运行时的实际值 (类、函数、对象等)
编译后结果完全被移除。生成的 .js 文件中没有这行代码。保留。生成的 .js 文件中会有 require('express')import 语句。
运行时影响无。不会增加打包体积,不会触发模块加载。有。会加载 express 模块,增加启动时间和打包体积。
主要用途用于变量类型注解、函数参数/返回值类型。用于实际调用类构造函数、使用常量、调用工具函数。
NestJS 装饰器场景推荐/必须 (当仅用于类型注解时)错误/多余 (如果只用来做类型注解)

2. 详细场景分析

场景 A:仅用于类型注解(最常见)

在 NestJS 控制器中,我们通常只需要 RequestResponse 来告诉 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 包中的 RequestResponse 实际上是 接口 (Interfaces) 而不是类 (Classes)。

  • 接口在 JavaScript 运行时是不存在的。
  • 因此,你永远无法在运行时 new Request() 或访问 Request.someStaticMethod
  • 结论:对于 expressRequestResponse永远应该使用 import type。如果你使用了 import { Request },虽然代码能跑(因为 TypeScript 编译器有时会宽容处理,或者 Babel 配置问题),但这引入了不必要的运行时依赖,且语义上是错误的。

3. 为什么 NestJS 文档或旧代码有时混用?

  1. 历史原因:在较旧的 TypeScript 版本(3.8 之前)中,没有 import type 语法,开发者被迫使用 import { ... },然后依靠编译器配置 isolatedModules 或 babel 插件来剔除类型。
  2. 惯性:很多老项目或教程没有更新写法。
  3. Babel/SWC 配置:某些构建配置会自动将仅用于类型的 import { ... } 转换为 import type,但这依赖于工具链配置,不如显式写出 import type 安全及明确。

4. 最佳实践建议

在 NestJS (以及任何现代 TypeScript 项目) 中:

  • 默认规则:如果你引入的东西只出现在冒号 : 后面(类型注解)、<T> 泛型中、或 implements 后面,请始终使用 import type
  • 针对 Express/Fastify:由于 RequestResponse 纯粹是接口,必须使用:
    import type { Request, Response } from 'express';
    // 或者
    import type { FastifyRequest, FastifyReply } from 'fastify';
    

这样做可以确保你的代码在任何构建配置下都是最优的,并且清晰地表达了意图:“我只需要这个类型,不需要加载这个模块”。