4、TS中的模块化

1,668 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

随着代码的逐渐增加, 前端越来越需要把不同的代码放置在不同的模块中, TS也是如此设计, 在 ES 出现之前,就存在一些模块化标准:commonjs、amd、cmd、umd、system、esnext。

关于模块化的相关配置

ts 中和模块配置相关的配置如下:

配置名称含义
module设置编译结果中使用的模块化标准
moduleResolution设计解析模块的模式
noImplicitUseStrict编译结果中不包含"use strict"
removeCommonts编译结果移除注释
noEmitOnError错误时不产生编译结果
esModuleInterop启用 es 模块化交互非 es 模块导出

本文只讨论 TS中如何书写模块化语句编译结果使用的是什么模块化标准

TS中如何书写模块化语句

本文不讨论 ES6 之前的模块化, 群魔乱舞的时代。目前统一使用 ES6 的模块化标准。

ES6 导入导出

    //导出 a.ts
    export const name = 'aoli'
        export function sum(a: number, b: number): number {
        return a + b
    }
    
    //导入 b.ts
    import { name, sum } from './a'

注意点:在导入模块时 文件路径千万不要加 ts 后缀名。因为编译之后是 JS代码, 就不能导入 TS 文件了。

编译结果中的模块化

编译结构是使用什么 模块化 标准是可以配置的。

tsconfig.json"compilerOptions.module" 该配置是指定编译的结果使用什么模块化标准

"compilerOptions.module : es6" 分析编译结果, 长什么样子

image.png

很明显没有区别, 因为使用的是 ES6 书写, 配置文件中指定的编译结果也是 ES6 所以没有区别。

"compilerOptions.module : commonjs" 分析编译结果, 长什么样子

    // ========== a.ts ==========
    //编译前
    export const name = 'aoli'
    export function sum(a: number, b: number): number {
        return a + b
    }
    export default function () {

    }
    //编译后
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.sum = exports.name = void 0;
    exports.name = 'aoli';
    function sum(a, b) {
        return a + b;
    }
    exports.sum = sum;
    function default_1() {
    }
    exports.default = default_1;

    exports.sum = sum;
    
    //========== b.ts ==========
    //编译前
    import def,{ name, sum } from './a'
    console.log(def,name, sum(1, 2),def)
    
    //编译后
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    const a_1 = require("./a");
    console.log(a_1.default, a_1.name, (0, a_1.sum)(1, 2), a_1.default);

编译后 Object.defineProperty(exports, "__esModule", { value: true }); 实际是 exports.__esModule = true 默认导出变成了给 exports.default 赋值。

TS 中导入问题

由于本文目前的环境是 node 开发环境,当使用 ES6 导入 path 模块时如下

import path from 'path' //ts 会报错

报错原因:这里使用的是 默认导入,根据上面的编译结果可知, 这里的默认导入编译之后是取 default 属性, 主要原因还是 nodejs 的 path 模块不是使用 ts 编写的。因为 nodejs 使用 commonjs 模块化标准, 默认导出使用 module.exports = ...ts 的编译是认为 default 属性才是 默认导出

解决方案:

import { resolve } from 'path' 

导入 path 模块中需要被使用的模块

为什么这样就可以解决问题呢? 查看一下编译结果

//编译前
import {resolve} from 'path'
resolve()

//编译后
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
(0, path_1.resolve)();

对边编译结果会发现, 编译后的这段 const path_1 = require("path"); (0, path_1.resolve)(); 是符合 commonjs 的模块化语句的。

如果一定要是用 全部导入在使用有不报错该怎么做呢?

import * as path from 'path'

可以使用 ES6 全部导出。

但是强逼症一定要是用 import path from 'path' 这种写法, 怎么办?

TS 官方其实也想到了上面的问题, 所以给了一个配置 "compilerOptions.esModuleInterop : true" 表示 启用 es 模块化交互非 es 模块导出 , 但会导致在编译结果里面多出现十几行的代码来处理上面的问题

//编译前
import path from 'path'
path.resolve()

//编译后 
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
path_1.default.resolve();

生成了一个 __importDefault 辅助函数来解决这个问题。

(mod && mod.__esModule) ? mod : { "default": mod }; 表示:传入的模块有 __esModule 属性就不做处理,没有则给这个模块等于一个对象 default 属性指向导入的模块

如何在 TS 总书写 commonjs 模块化代码

其实和 node 中书写一样, 没有什么区别。

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

//导入
const a = require('./a')
console.log(a)

但是导出的模块 a 没有类型检查, aany 类型。

为了获得类型检查, 就一定要按照 ts 的标准来书写导出语句

//导出
exports = {
    name: "test",
    sum(a: number, b: number): number {
        return a + b
    }
}

//导入
import a = require('./a')

写法会很奇怪, 所以推荐在使用 ts 的时候, 使用 ES6 模块标准。

模块解析

模块解析: 应该从什么位置寻找模块。

ts 中有两种模块解析策略

  • classic: 经典,出现在 ES6 之前的策略。(目前已过时)

  • node: node解析策略。(唯一的变化, 是将 js 替换为 ts)

总结

推荐在最新的项目中使用 ES 模块化标准, 一面出现不必要的问题。