一起养成写作习惯!这是我参与「掘金日新计划 · 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" 分析编译结果, 长什么样子
很明显没有区别, 因为使用的是 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 没有类型检查, a 是 any 类型。
为了获得类型检查, 就一定要按照 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 模块化标准, 一面出现不必要的问题。