TypeScript模块化开发指南:ES6与CommonJS对比及常见问题解决

作者:拾光

时间:2026-10-26

浏览:287

本文深入解析TypeScript中的模块化机制,对比ES6标准模块与CommonJS的差异。重点讲解在Node.js环境中引入非ESM模块(如fs)时的类型错误及编译问题,提供解构导入、命名空间导入及配置esModuleInterop的三种解决方案,并简述命名空间的历史背景与现状。

文章目录

前言

随着项目代码规模不断扩大,通过拆分模块进行组合管理成为提升后期维护效率的关键手段。

除了commonJS和es6之外,TypeScript同样原生支持模块化开发。

TypeScript中ES6模块化语法实现

自ES6(即ES2015)发布以来,TypeScript便遵循其模块化标准,采用导出(export)和导入(import)机制来管理模块。

  • 具体写法如下:

导出模块:

myModule.ts
    export const name: number = 123
    export const getUser = (user: string): void => {
      console.log(user)
    }

导入模块:

index.ts
    import { name, getUser } from "./myModule";
    getUser(name)
  • 值得注意的是,TypeScript的智能提示功能在此场景下表现优异。

若未显式引入变量,编辑器会通过波浪线提示建议的引入路径。

前言

直接输入变量名并按下enter键,即可自动补全引入语句。

前言

前言

尽管智能提示功能强大,但仍需注意以下关键事项。

注意事项详细
获取智能提示有条件但是要注意的是: 这个提示只适用于export, 不适用于export default。因为export有固定的变量名,而export default导出没有名字和引入时随意起名字, TS不会去猜测
import引入的文件名import引入的文件名不能带上.ts, 因为会参与编译的过程, 编译后的结果为js,找不到ts这个文件

TypeScript中CommonJS模块化语法实现

若沿用传统写法,代码结构如下:

// 导出
module.exports = {
    sum (a: number, b: numnber) {
        return a + b
    }
}

//引入
const mymodule = require('./xxx')

此时观察到mymodule的类型变为any,导致丧失类型检查能力。

  • 在ts中采用特定写法,既能获得完整的类型检查,又能保持与前述写法一致的编译结果。
// 导出
exports = {
    sum (a: number, b: numnber) {
        return a + b
    }
}

//引入
import mymodule = require('./xxx')

可以看出两种写法存在明显差异。

因此,建议优先采用ES6标准,因其代表了后续的发展方向。

解决模块导入错误的方法

首先看一个示例,我们在代码中引入node中的fs模块。

TypeScript源文件内容:

    import fs from "fs";
    fs.readFileSync('./')

编译后的JavaScript结果:

    // tsconfig.json中设置module: CommonJs, 结果如下

    exports.__esModule = true;
    var fs_1 = require("fs");
    fs_1["default"].readFileSync('./');

此时会观察到一些潜在问题。

  • 1. 在TypeScript源文件中书写时会报错。
  • 2. 编译结果中,代码使用了default属性.readFileSync。

由于编译后的fs对象不包含default属性,因此运行时报错。

报错原因分析

 import fs from "fs";

fs模块并非遵循标准ES Module导出规范,而是默认使用module.exports={}形式导出。直接import引入时,编译结果会附加default属性,而fs中并不存在该属性,从而导致错误。

解决方案

  • 方案一:解构出具体方法使用,这样就不会依赖default属性。

TypeScript源文件内容:

    import { readFileSync } from "fs";
    readFileSync('./')

编译后的JavaScript结果:

    exports.__esModule = true;
    var fs_1 = require("fs");
    (0, fs_1.readFileSync)('./');
  • 方案二:使用* as fs进行全部导入,将module.exports内的所有成员导入。

TypeScript源文件内容:

    import * as fs from "fs";
    fs.readFileSync('./')

编译后的JavaScript结果:

    exports.__esModule = true;
    var fs = require("fs");
    fs.readFileSync('./');
  • 方案三:将esModuleInterop配置为true,使TypeScript能够识别是否导出ES Module标准形式,并作出相应处理。
tsconfig.json

{
  "compilerOptions": { 
    // 启用es模块化交互而非es模块导出
    "esModuleInterop": true
  }
}

TypeScript源文件内容:

    import fs from "fs";
    fs.readFileSync('./')

编译后的JavaScript结果:

// 生成辅助函数__importDefault
// 此时如果不是属于esmodule形式导出, ts会默认添加default属性,值设置为本身
    var __importDefault = (this && this.__importDefault) || function (mod) {
        return (mod && mod.__esModule) ? mod : { "default": mod };
    };
    exports.__esModule = true;
    var fs_1 = __importDefault(require("fs"));
    fs_1["default"].readFileSync('./');

补充:TypeScript命名空间

TypeScript拥有自己的模块格式,称为命名空间(namespaces),该标准早于ES模块标准。

这种语法在创建复杂的定义文件时具有诸多实用功能,且在DefinitelyTyped中仍被广泛使用。尽管该特性未被废弃,但命名空间中的大部分功能已存在于ES Modules中,官方建议采用后者以与JavaScript的发展方向保持一致。

总结

至此,关于如何在TypeScript中使用模块化及注意事项的介绍已完成。如需了解更多TypeScript模块化相关内容,请搜索本站历史文章或继续浏览后续文章,感谢大家的支持!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章