浅析TypeScript中的命名空间

发布于:2023-04-02 ⋅ 阅读:(528) ⋅ 点赞:(0)

引言

在 TypeScript 1.5 之前只支持命名空间的方式来组织代码,避免全局命名空间污染和命名冲突问题,在当时这是一种非常好的实践方式,虽然现在更推荐使用 ESModule 的方式来进行模块化地管理,但是使用 namespace 来构造命名空间的方式依旧被使用,还是建议大家学习下。

我这里会把当时学习和使用 namespace 时的一些疑惑列举出来,看看自己有没有不清楚的点

namespace 的实现原理

直接通过代码来看实现原理

namespace Qing {
  interface Person {
    name: string;
    age: number;
  }

  let name = "qing";
  let age = 18;

  function createPerson(name: string, age: number): Person {
    return { name, age };
  }
}

编译成 js 后

"use strict";
var Qing;
(function (Qing) {
  let name = "qing";
  let age = 18;
  function createPerson(name, age) {
    return { name, age };
  }
})(Qing || (Qing = {}));

简单地解释下,就是声明了一个全局变量,然后利用函数立即执行表达式构建了一个局部的作用域,使得外部无法访问到命名空间中的变量和函数。

export

仔细观察上面的代码其实可以发现,Qing 这个命名空间本身都访问不到自己内部的属性了,这就有点绷不住了。原本的意图是要限制外部的访问权限,结果连自己都访问不了,这个时候就需要使用 export 来进行内容的导出了。

作用

我们在使用 esmodule 来进行导出的时候经常用到 export 字段,在 namespace 中也是一样,用来导出命名空间中的属性。

namespace Qing {
  interface Person {
    name: string;
    age: number;
  }

  let name = "qing";
  export let age = 18;

  export function createPerson(name: string, age: number): Person {
    return { name, age };
  }
}

编译结果

"use strict";
var Qing;
(function (Qing) {
  let name = "qing";
  Qing.age = 18;
  function createPerson(name, age) {
    return { name, age };
  }
  Qing.createPerson = createPerson;
})(Qing || (Qing = {}));

我们可以看到之前声明 age 变量和 createPerson 方法被挂在到了 Qing 这个变量上去了,这样命名空间 Qing 就能访问到导出的属性了。

使用场景

其实官网推荐是将你想要导出的内容都使用 export 来进行导出,据我个人的看过的代码来说,有很多的代码没有对命名空间内部的属性进行导出,但是在外部却在使用。

例一

// global.d.ts
declare namespace Qing {
  interface Person {
    name: string;
    age: number;
  }
}

// index.ts
function createPerson(name: string, age: string): Qing.Person {
  return { name, age };
}

例二

// global.d.ts
export namespace Qing {
  interface Person {
    name: string;
    age: number;
  }
}

// index.ts
import { Qing } from "global.d.ts";
function createPerson(name: string, age: string): Qing.Person {
  return { name, age };
}

虽然上面的两个例子都没有使用 export 来导出 Person 这个类型,但是代码并没有报错,我来解释下原因:

因为在 d.ts 文件中声明命名空间时,这个命名空间声明的内容会自动默认全局可见,而无需特别使用 export 进行导出

但是某些场景下,你在不主动 export 的时候,代码会进行报错。

// utils.ts
export namespace Util {
  function getLength(str: string): number {
    return str.length;
  }
}

// index.ts
import { Util } from "./utils";

const str = "hello, world";
const length = Util.getLength(str);
console.log(length);

报错信息:

image.png

出现这种情况的时候,那么就需要使用 export 将命名空间内部定义过的属性暴露出去。

// utils.ts
export namespace Util {
  export function getLength(str: string): number {
    return str.length;
  }
}

为 getLength 这个函数前面加上 export 即可解决。

前面有说到在 d.ts 文件中不加 export 也能访问到,那么我这里把 utils.ts 这个文件改成 utils.d.ts 文件,是不是就不需要为 getLength 函数前面添加 export?

很可惜在 d.ts 文件中是不支持函数定义的,也就是带有具体实现的函数。

结论:

对于 ts 文件,文件中的命名空间中的内容如果想要被外部访问到,必须要在前面加上 export。

对于 d.ts 文件,文件中的命名空间中的内容如果想要被外部访问到,不强制要求加上 export。

本文含有隐藏内容,请 开通VIP 后查看