前言

Typescript将内部模块称为命名空间,外部模块称为模块,我们就来介绍下模块的用法。

命名空间

一般情况下,当你在一个新的Typescript文件中写下代码时,它会处于全局命名空间中,比如你声明一个变量,该变量是在全局可以访问到的。

  1. // foo.ts
  2. const foo = 123;
  3. // bar.ts
  4. const bar = foo;

使用全局变量空间是危险的,因为它会与文件内的代码命名冲突,为了确保变量不会泄漏至全局变量,我们可以使用文件模块来解决这个问题。
如在 TypeScript 文件的根级别位置含有 import 或者 export,它就会在这个文件中创建一个本地的作用域,如果其他文件想要使用变量,必须显示的导入该变量,上述例子可以进行以下修改:

  1. // foo.ts
  2. export const foo = 123;
  3. // bar.ts
  4. import { foo } from './foo';
  5. const bar = foo; // allow

避免全局变量污染的第二种方法是使用 namespace 的外部的模块

  1. namespace Utility {
  2. export const foo = 123;
  3. }
  4. console.log(Utility.foo) // 123

命名空间是支持嵌套的。因此,你在在一个命名空间下嵌套另外一个命名空间 ,对于快速的演示和移植旧的 JavaScript 代码。推荐使用namespace 的外部的模块。

文件模块

言归正传,既然文件模块有如此强大的功能,我们就来学习他的一些用法。

模块导出

任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出,当我们需要导出大量的内容时,可以采用这种方式。

  1. export const someVar = 123;
  2. export const numberRegexp = /^[0-9]+$/;
  3. export type someType = {
  4. foo: string;
  5. };
  6. export interface StringValidator {
  7. isAcceptable(s: string): boolean;
  8. }
  9. export class ZipCodeValidator implements StringValidator {
  10. isAcceptable(s: string) {
  11. return s.length === 5 && numberRegexp.test(s);
  12. }
  13. }

除了上面的写法,还可以以另一种方式书写

  1. const someVar = 123;
  2. const numberRegexp = /^[0-9]+$/;
  3. export { someVar, numberRegexp };

或者可以重新命名变量导出

  1. const someVar = 123;
  2. export { someVar as aDifferentName };

重新导出
通过重命名,部分导出从另一个模块导入的项目。想要扩展某个模块的功能可以使用重新导出,因为重新导出可以不要去改变原来的对象,而是导出一个新的实体来提供新的功能。

  1. export { someVar as aDifferentName } from './foo';

从其他模块导出后部分导出

  1. export { someVar } from './foo';

从其他模块导出后整体导出

  1. export * from './foo';

模块导入

使用import关键字导入一个变量或者类型,需要明确的导出模块名称时使用这种方式。

  1. import { someVar, someType } from './foo';

可以对导入对模块进行重命名

  1. import { someVar as aDifferentName } from './foo';

用星号(*)指定一个变量,所有输出值都加载在这个变量上面,进行整体导入

  1. import * as foo from './foo';

具有副作用的导入模块,一些模块会设置一些全局状态供其它模块使用,一般不推荐这么做

  1. import "./my-module.js";

默认导入/导出

默认导出使用 default关键字标记;并且一个模块只能够有一个default导出,如果仅导出单个 class 或 function,使用 export default。
使用方式:

  • 在一个变量之前(不需要使用 let/const/var);
  • 在一个函数之前;
  • 在一个类之前
    1. // some var
    2. export default (someVar = 123);
    3. // some function
    4. export default function someFunction() {}
    5. // some class
    6. export default class someClass {}
    导入使用import即可
    1. import foo from './foo';

export = 和 import = require()

CommonJS和AMD的环境里都有一个exports变量,为了支持CommonJS和AMD的exports, TypeScript提供了export =语法.
export =语法定义一个模块的导出对象。 这里的对象一词指的是类,接口,命名空间,函数或枚举

  1. export class ZipCodeValidator implements StringValidator {
  2. isAcceptable(s: string) {
  3. return s.length === 5 && numberRegexp.test(s);
  4. }
  5. }
  6. export = ZipCodeValidator

若使用export =导出一个模块,则必须使用TypeScript的特定语法import module = require(“module”)

  1. import ZipCodeValidator = require("./ZipCodeValidator");

重写类型的动态查找

在项目里,也可以通过 declare module ‘somePath’ 来声明一个全局模块的方式,用来解决查找模块路径的问题

  1. // globals.d.ts
  2. declare module 'foo' {
  3. // some variable declarations
  4. export var bar: number;
  5. }
  6. // other.ts
  7. import * as foo from 'foo';

模块懒加载

编译器会检测是否每个模块都会在生成的JavaScript中用到。 如果一个模块只在类型注解部分使用,并且完全没有在表达式中使用时,就不会生成 require这个模块的代码,如下例子:

  1. import foo = require('foo');
  2. var bar: foo;
  3. // 编译的js
  4. let bar

在某些情形下,只想在需要时加载模块,此时需要仅在类型注解中使用导入的模块名称,而不是在变量中使用。那么在编译javascript时,就不会生成 require这个模块的代码.
如下基于commonjs例子中,使用typeof关键字来获取foo的类型

  1. import foo = require('foo');
  2. export function loadFoo() {
  3. // 这是懒加载 foo,原始的加载仅仅用来做类型注解
  4. const _foo: typeof foo = require('foo');
  5. // 现在,你可以使用 `_foo` 替代 `foo` 来做为一个变量使用
  6. }

一个同样简单的 amd 模块(使用 requirejs)

  1. import foo = require('foo');
  2. export function loadFoo() {
  3. // 这是懒加载 foo,原始的加载仅仅用来做类型注解
  4. require(['foo'], (_foo: typeof foo) => {
  5. // 现在,你可以使用 `_foo` 替代 `foo` 来做为一个变量使用
  6. });
  7. }

使用场景:

  • 在 web app 里, 当你在特定路由上加载 JavaScript 时;
  • 在 node 应用里,当你只想加载特定模块,用来加快启动速度时

总结

我们了解到来文件模块的众多使用方法,以及相应使用方法下的应用场景,我们可以根据业务需要采取合适的模块导入导出方式。

参考