TypeScript 命名空间和模块

介绍

TypeScript 1.5里术语名已经发生了变化:

  • “内部模块”现在称做“命名空间”。也就是说像 module X { } 这样的写法,在当前推荐的写法中应该是 namespace X { }

  • “外部模块”现在简称为“模块”,通常通过 importexport 关键字来实现模块间的功能共享和代码组织。

这是为了让 TypeScript 与 ECMAScript 2015(ES6)的术语保持一致,从而减少混淆并提高与主流 JavaScript 标准的兼容性。

命名空间

命名空间(Namespace)用于组织和封装相关的代码,以避免全局命名冲突,并提供更好的代码结构和模块化。

  • 基本语法
    • 使用 namespace 关键字来创建命名空间
  • 成员
    • 命名空间可以包含以下类型的成员:变量、函数、类、接口

示例:

namespace MathUtils {
  // 要使命名空间中的成员在外部可访问,需要使用 export 关键字
  export function add(a: number, b: number): number {
    return a + b;
  }

  export function subtract(a: number, b: number): number {
    return a - b;
  }
}

// 使用命名空间中的函数
let result1 = MathUtils.add(5, 3);
let result2 = MathUtils.subtract(8, 2);
console.log(result1, result2); // 8  6

在上述代码中,MathUtils 是一个命名空间,其中包含了 addsubtract 两个函数。通过 namespace 关键字创建了这个命名空间,并使用 export 关键字使其中的函数可以在命名空间外部被访问。

可以在不同的位置多次定义同一个命名空间,TypeScript 会将它们的成员进行合并:

namespace MyNamespace {
  export function function1() {
    // 实现
  }
}

namespace MyNamespace {
  export function function2() {
    // 实现
  }
}

多文件中的命名空间

当涉及到多个文件中的命名空间时,要使命名空间中的成员在外部可访问,需要使用 export 关键字和 import 关键字。

假设我们有两个文件:file1.tsfile2.ts
file1.ts

export namespace MyNamespace {
  export function function1() {
    console.log('Function 1 in MyNamespace');
  }
}

file2.ts

export namespace MyNamespace {
  export function function2() {
    console.log('Function 2 in MyNamespace');
  }
}

在另一个文件 main.ts 中使用 :

import { MyNamespace } from './file1';
import { MyNamespace } from './file2';

MyNamespace.function1();
MyNamespace.function2();

file1.tsfile2.ts 中使用 export 关键字导出相关的内容,然后在需要使用的文件(如 main.ts)中使用 import 语句来引入。

别名

使用 import q = x.y.z 的方式给命名空间中的对象创建别名,来简化命名空间的操作。

还可以用这种方法为任意标识符创建别名,也包括导入的模块中的对象。

首先,创建一个包含命名空间的文件 namespaceFile.ts

namespace MyComplexNamespace {
  export namespace SubNamespace {
    export function SomeFunction() {
      console.log('This is a function in the namespace.');
    }
  }
}

然后,在另一个文件 main.ts 中使用别名来简化对命名空间中函数的调用:

import q = MyComplexNamespace.SubNamespace.SomeFunction;

q();

通过 import q = MyComplexNamespace.SubNamespace.SomeFunction; 给命名空间中的函数 SomeFunction 起了一个短别名 q,然后在后续的代码中直接使用 q() 来调用该函数,而无需再写完整的命名空间路径。

注意:没有使用require关键字,而是直接使用导入符号的限定名赋值。

当使用直接使用导入符号的限定名进行赋值来创建别名时:

import q = MyComplexNamespace.SubNamespace.SomeFunction;

这类似于使用 var 来创建变量,但有一些重要的区别:

  • 适用于类型和具有命名空间含义的符号: 不仅可以用于普通的值,还可以用于类型、命名空间等。
  • 对于值,创建的是新的引用: 这意味着对别名所做的修改不会影响原始导入的值。

例如:

// originalModule.ts
export let originalValue = 10;

// main.ts
import MyValue = originalModule.originalValue;

// 修改 MyValue 的值,不影响原始数据
MyValue = 20; 
console.log(originalModule.originalValue); // 仍然是 10,没有被修改

模块

在 TypeScript 中,“外部模块”简称为“模块”。

模块是用于组织和封装代码的重要机制。模块可以将相关的功能分组到独立的单元中,具有更好的可维护性、可复用性和可测试性。

模块中的变量、函数、类等成员具有模块级别的作用域,不会污染全局作用域。

  • 定义在一个模块里的变量,函数,类等等在模块外部是不可见的。除非使用export 关键字明确导出它们。
  • 如果想使用其它模块导出的变量,函数,类,接口等的时候,必须要使用import 关键字导入它们,

模块是自声明的;两个模块之间的关系是通过在文件级别上使用importsexports建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。

导出

使用 export 关键字导出模块的成员,使其可以在其他模块中被访问。

任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出:

export function myFunction() {
  // 函数实现
}
export class MyClass {
  // 类的定义
}
export interface StringValidator {
  // 接口的定义
  isAcceptable(s: string): boolean;
}
export class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    // 具体实现
  }
}

导出可以重命名:

export { ZipCodeValidator as mainValidator };

默认导出

每个模块都可以有一个默认导出, 默认导出使用 default关键字标记。
一个模块只能够有一个default导出。
导入默认导出时可以使用任何名称。

// file1.ts  类作为默认导出
export default class MyDefaultClass {
  // 类的实现
}

// file2.ts  变量作为默认导出
export default const myDefaultVariable = 10;

// myModule.ts
// 函数作为默认导出
export default function myDefaultFunction() {
  // 函数实现
}

// other.ts 中导入
import myDefaultFunctionfrom './myModule'; 
// 导入并将其命名
import anotherName from './myModule';

导入

使用 import 关键字从其他模块导入需要的成员。
可以使用相对路径(如 ./myModule )或绝对路径(如 @myModule ,需要配置路径别名)来导入模块。

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

可以对导入内容重命名:

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();

将整个模块导入到一个变量,并通过它来访问模块的导出部分:

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

具有副作用的导入模块
尽管不推荐这么做,一些模块会设置一些全局状态供其它模块使用。 这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块:

import "./my-module.js";

当使用 import 来引用模块时,TypeScript 编译器会按照特定的规则来查找模块的类型信息。
它会首先在指定的路径下查找 .ts.tsx.d.ts 文件。如果这些文件都不存在,编译器就会去查找外部模块声明,这些声明通常在 .d.ts 文件中。
import 提供了更明确和可靠的模块引用方式,它清晰地表明了模块之间的依赖关系。

export =import = require()

CommonJS和AMD的环境里都有一个exports变量,这个变量包含了一个模块的所有导出内容。

CommonJS和AMD的exports都可以被赋值为一个对象, 这种情况下其作用就类似于 es6 语法里的默认导出,即 export default语法了。虽然作用相似,但是 export default 语法并不能兼容CommonJS和AMD的exports

为了支持CommonJS和AMD的exports, TypeScript提供了export =语法。

export =语法定义一个模块的导出对象。 这里的对象一词指的是类,接口,命名空间,函数或枚举。

若使用export =导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块。

export = 用于将模块的默认导出设置为一个特定的值或对象:

// myModule.ts
class MyClass {
  // 类的实现
}

export = MyClass;

import = require() 用于从使用 export = 导出的模块中导入默认导出:

// other.ts
import myModule = require('./myModule');

这种语法在将基于 CommonJS 风格的模块集成到 TypeScript 项目中时可能会用到,但在现代 TypeScript 开发中,更推荐使用基于 ES6 模块的 importexport 语法,因为它们更符合现代 JavaScript 的标准和趋势。
例如,使用 ES6 模块的对应方式可能是:

// myModule.ts 导出
export default class MyClass {
  // 类的实现
}

// other.ts 导入
import myModule from './myModule';

命名空间和模块的陷阱

对模块使用/// <reference>

一个常见的错误是使用/// <reference>引用模块文件,应该使用import
使用 /// <reference> 来引用一个模块文件,当模块文件的位置发生变化或者项目的结构有所调整时,可能会导致引用失败或者出现难以预料的错误。

  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值