Skip to main content

Modules

从ECMAScript 2015开始,JavaScript有了模块的概念。在TypeScript中,也有相同的概念。

  • 模块在自己的作用域执行,而不是在全局作用域;这意味着声明在一个模块中的变量、函数、类等对外不可见,除非显示使用export导出。相反地,为了使用从另一个模块导出的变量、函数、类、接口等,需要使用import导入。

  • 模块是声明性的,模块之间的关系在文件级别以导入和导出来指定。

  • 模块使用模块加载器相互导入。在运行时,模块加载器负责在执行模块之前定位和执行模块的所有依赖项。JavaScript中使用的知名模块加载器是Node.js用于CommonJS模块的加载器和Web应用程序中AMD模块的RequireJS加载器。

在TypeScript中,就像在ECMAScript 2015中一样,任何包含顶级导入或导出的文件都被视为模块。相反,没有任何顶级导入或导出声明的文件被视为脚本,其内容在全局范围内可用(因此也适用于模块)。

Export

导出声明

任何声明可以通过export关键字导出:

// demo.ts
export interface StringValidator {
isAcceptable: (s: string) => boolean
}

// test.ts
import { StringValidator } from './demo.ts'

class Validator implements StringValidator {
isAcceptable(s: string) {
return typeof s === 'string'
}
}

export { Validator }

重新导出

通常模块会扩展其他模块,并部分地暴露一些功能。重新导出不会在本地导入它,也不会引入本地变量。

export { Validator as BaseValidator } from './test.ts'

export * from './test.ts'

Import

命名导入

从模块中导入内容可通过以下一种导入形式之一:

// 命名导入
import { StringValidator } from './demo.ts'

// 导入重命名
import { StringValidator as MyStringValidator } from './demo.ts'

// 全部导入
import * as MyValidator from './demo.ts'

还有一种导入模块以实现副作用的效果,虽然不建议这样做,但有些模块会设置一些全局状态,其他模块可以使用这些状态。这些模块可能没有任何导出,或者使用者对它们的导出不感兴趣。要导入这些模块,请使用以下方式:

import "./my-module.js";

导入类型

TypeScript 3.8及以上版本,可使用importimport type导入类型:

api.ts
// api.ts
export interface APIResponseType {
status: number,
type: string,
data: object
}

export interface APIRequestType {
url: string,
method: string,
data: object
}
request.ts
import { APIResponseType } from './api';
import type { APIRequestType } from './api';
import { APIRequestType, type APIResponseType} from './api';

任何明确标注的类型导入都将从JavaScript中被移除,而像Babel这样的工具可以通过 isolatedModules 编译器标记对你的代码进行更好的假设。

默认导出

每个模块可以选择性地导出一个默认导出。默认导出使用关键字 default 标记;每个模块只能有一个默认导出。默认导出使用不同的导入形式进行导入。

// jQuery.d.ts
declare let $: JQuery;
export default $;

// index.ts
import $ from "jquery";
$("button.continue").html("Next Step...");

类和函数声明可以直接作为默认导出,且默认导出的类和函数声明的名称是可选的:

// class example
export default class ZipCodeValidator {
static numberRegexp = /^[0-9]+$/;
isAcceptable(s: string) {
return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
}
}

import validator from "./ZipCodeValidator";
let myValidator = new validator();

// function example
export default function (s: string) {
return s.length === 5 && numberRegexp.test(s);
}

import validate from "./StaticZipCodeValidator";

当然,默认导出也可以只是值:

export default 'abc';

const obj = {};
export default obj;

export * as

TypeScript 3.8以上,可以使用 export * as ns 作为一种简写形式,将另一个模块重新导出并指定名称:

// index.ts
export * as utilities from "./utilities";

这将获取模块的所有依赖项,并将其作为导出字段,可以像这样导入它:

import { utilities } from "./index";

export = & import = require()

通常,CommonJS 和 AMD 都有一个包含模块所有导出的exports对象的概念。它们还支持使用自定义单个对象替换exports对象。默认导出旨在充当此行为的替代方式;然而,这两者是不兼容的。TypeScript 支持使用 export = 来模拟传统的 CommonJS 和 AMD 工作流程。

export = 语法指定了一个从模块导出的单个对象。该对象可以是类、接口、命名空间、函数或枚举。import = require()用来导入export = 导出的对象。

// api.ts
class API {}
export = API;

// index.ts
import API = require('./api');

环境模块

在Node.js中,大多数任务是通过加载一个或多个模块来完成的。我们可以将每个模块定义在自己的.d.ts文件中,使用顶级导出声明,但是将它们写成一个更大的.d.ts文件更加方便。为了实现这一点,我们使用类似于环境命名空间的结构,但是我们使用module关键字和被引入的模块的名称(用引号括起来),以便在后续的导入中使用。

declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(
urlStr: string,
parseQueryString?,
slashesDenoteHost?
): Url;
}
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export var sep: string;
}

可以使用import url = require("url");或者import * as URL from "url"加载以上模块:

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("https://...");

简写形式

如果不想花时间在使用新模块之前编写声明,可以使用简化的声明来快速开始,但是从简化模块中导入的所有内容都将具有any类型:

declare module "hot-new-module";

import x, { y } from "hot-new-module";
x(y);