TypeScript 中的 Module
本文主要介绍 TS 中的 module 与 non-modules 的区别;.d.ts
文件的作用;以及模块路径的解析规则;
TS 中规定顶层存在 import 、export 关键字的代码文件被认为是一个模块,没有顶层 import 、export 的文件认为是一般脚本。
模块与脚本的区别
模块 modules 与脚本 non-modules 存在以下几个方向上的区别, 这是 JavaScript 中模块与脚本的区别(TypeScript 同样)。
- 作用域上的区别
<!---->
- module 存在自己的作用域。在模块中定义的变量、函数、类都存在于自己的作用域上,外部模块和脚本代码不可见。
- non-modules 运行在 global scope 上。顶层定义的变量、函数、类都存在于全局作用域上。外部模块和脚本均可访问。
<!---->
- 导出和导入
<!---->
- module 使用 import export 关键字来分别控制从其他模块导入代码段(变量、函数、类等)以及导出模块中的代码段。
- non-modules 中定义的函数和变量在全局作用域中,因此不需要额外的导入导出操作,直接可全局访问。
<!---->
- 代码管理
<!---->
- module 便于代码切分组织,可按照抽象层次或者功能进行模块划分,便于代码组织。
- 脚本代码一般都是写在统一的几个文件中,需要注意变量的访问顺序,并且有污染全局作用域的风险需要规避。
<!---->
- 依赖管理
<!---->
- 模块依赖是通过 import 来显示指定依赖关系,允许更好的控制依赖关系,可以降低命名上冲突的可能性。
- 脚本的依赖关系是隐藏的(需要认真阅读代码,逐层debug才能分析清楚),脚本按照架子啊的顺序执行,脚本之间加载的顺序很重要【脚本加载的顺序变化可能出现: 函数未找到,变量未定义等错误 】
<!---->
- 加载时序
<!---->
- 模块支持异步加载,加载完成后在进行代码执行,可以减少HTML文档解析上的阻塞。
- 脚本是经典的同步加载,在HTML中出现同步脚本则需要等待代码加载并执行完成后HTML解析才继续执行。通常代码中除了部分需要提前执行的代码(权限检查,用户校验等),其余的都放在HTML文档的末尾进行加载(可显式异步加载)执行。
// oneModule.js
export function foo(params) {
//...
}
export class Bar {
constructor() {
//...
}
}
// twoModule.js
import { foo, Bar } from './oneModule.js';
export function run(){
const ins = new Bar();
// ...
return foo(ins);
}
TS 中模块加载机制
模块之间建立关系是靠 import 和 export 来配合使用的,模块加载的时候路径指定有两种方式一种是相对路径另一种是非相对路径。相对路径根据当前文件位置计算索引找到文件位置,非相对位置模块导入编译器会从包含导入的文件目录开始一次向上级目录遍历,尝试匹配到对应的文件。
.d.ts 文件的用法以及相关内容
在TypeScript中,.d.ts
文件通常用来定义 JavaScript 库或模块的类型声明。它们是用于描述已存在的JavaScript代码的类型信息的文件,主要描述JavaScript 模块的结构(导出类的参数类型,函数签名格式等)目的是方便在ts代码中进行类型检查和智能感知提醒。当使用TypeScript编写代码时,可以使用类型声明文件来获得对JavaScript库或模块的类型检查和智能感知支持。这些声明文件通常以.d.ts
为扩展名。
通过引入适当的类型声明文件,可以有以下帮助:
- 类型检查:类型声明文件允许TypeScript编译器验证代码与声明文件中定义的类型是否匹配。这有助于在开发过程中捕获潜在的类型错误,并提供更强大的静态类型检查。
- 智能感知:类型声明文件为编辑器提供了有关库或模块的类型信息,从而提供了智能感知功能。这使得在编写代码时能够获得自动完成、参数提示和文档等功能,以增加开发效率。
- 文档化:类型声明文件还可以作为库或模块的文档。通过查看类型声明文件,可以了解库的可用函数、类、接口和类型等详细信息,从而更好地理解和使用它们。
案例,定义一个 worker 来实现 ThreeJS 中数据的处理:
onmessage(params) {
//
const data: Block[] = [];
... 大量计算
// 线程计算完成返回
postMessage({ fragments: data }, timestamp: params.timestamp);
}
declare module '*?worker' {
const workerConstructor:{
new (): Worker
};
export default workerConstructor
}
import './worker.d.ts';
import Worker from './worker.ts?worker'
function calculateAllBlocks(threadCount: number) {
for (let i =0; i< threadCount; i++) {
const worker = new Worker();
worker.onmessage = dataParse;
// 传入参数启动线程进行计算
worker.postMessage({..., timestamp: performance.now()});
}
}
function dataParse(workerEvent: MessageEvent) {
// 处理线程返回数据
}
这样我们能快速定义一个 worker.ts 线程处理模块代码,而不需要定义成js再作为参数传递进去加载执行。 main.ts 模块中使用了 Worker 类型也都合法, 原因就是导入了 worker.d.ts 来进行类型解释。 worker.d.ts 中使用了一个后缀模块声明通配符也就是“*?worker”,只有在导出模块上加上能通过校验的前后缀才能正确的匹配上(注意main.ts 中的导入)。TS中声明通配符只有在导出的时候使用了匹配上的后缀才会使用对应的类型来进行解释目标模块 。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。