011_TypeScript 第十一章( TS 的模块和命名空间 )

descript

模块

  • 前言 :

    JavaScript 在 ES2015 中引入了模块的概念, 我们的 JS 也开始进行了标准的模块化开发阶段, 在 TS 内沿用了这个概念

    在 TS 中, 从语法到概念, 其实和 JavaScript 中基本一致的

  • 因为我们主要是学习 TS, 在这里对模块的语法和概念做一个简单的复习

导出

  • 在一个文件中, 所有的声明都可以利用 export 关键字进行导出
  • 其实就是向外暴露该内容, 被其他模块使用

基础导出

  • moduleA.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 导出一个变量
export const num = 100

export const str = 'hello world'

export const reg = /^许小墨$/

// 导出一个函数
export function fn() {}

// 导出一个类
export class Student {}

export class Person extends People {}

// 导出一个接口
export interface Users {}

重新导出

  • 有的时候我们可能会在某一个文件内导入一段内容, 然后再次进行导出
  • modeuB.ts
1
export * from './moduleA'
  • 在这个 moduleB 文件中导入了 moduleA 文件的所有内容

  • 并且在 moduleB 中也进行了一次导出

  • 可能很多小伙伴就要问了, 这个有什么意义吗 ?

    • 其实是可以做一些模块合并的
  • 举个例子

    • moduleA.ts
    1
    export const num = 100
    • moduleB.ts
    1
    export const str = 'hello world'
    • moduleC.ts
    1
    export interface Users {}
    • moduleIndex.ts
    1
    2
    3
    4
    5
    export * from './moduleA'

    export * from './moduleB'

    export * from './moduleC'
    • 这样一来, 就相当于用 moduleIndex 文件对前面三个模块进行可一个整合导出

导出重命名

  • 有的时候, 我们在重新导出的时候, 有可能会遇到一些多个模块的重名问题

  • 或者说会遇到多个模块因为不是一个人书写的, 导致模块命名风格不统一

  • 我们在书写的时候, 就可以在导出的时候做一个重命名

  • 语法

    1
    export { num as current } from './moduleA'
    • 相当于在这个文件中导入了 moduleA 模块中的 num 数据
    • 并且进行了再次导出
    • 只不过进行再次导出的时候, 用的名字是 current 而已
  • 举个例子

    • moduleA.ts
    1
    export const n = 100
    • moduleB.ts
    1
    export const s = 'hello world'
    • moduleC.ts
    1
    export interface Aaa {}
    • moduleIndex.ts
    1
    2
    3
    4
    5
    export { n as num } from './moduleA'

    export { s as str } from './moduleB'

    export { Aaa as Users } from './moduleC'
    • 这样一来, 就相当于用 moduleIndex 文件对前面三个模块进行可一个整合导出
    • 并且对以上三个模块导出的内容进行了重命名
    • 等价于
    1
    2
    3
    4
    5
    6
    // 伪代码, 仅仅为了直观看一下上面代码的效果
    export const num = 100 // moduleA 中导出的 n

    export const str = 'hello world' // moduleB 中导出的 s

    export interface Users {} // moduleC 中导出的 Aaa

导入

  • 和上面导出是配套出现的

基础导入

  • 直接导出某一个模块的某个或者某些内容
  • moduleA.ts => 进行导出
1
2
3
4
5
export function study() {}

export function play() {}

export function sleep() {}
  • index.ts => 进行导入
1
import { study, play } from './moduleA'

导入重命名

  • 导入的时候, 有的时候, 我们可能不想用原先文件内导出的名字

  • 不管是因为命名冲突问题还是嫌原先的名字太长, 都有可能

  • 那么我们就可以进行重命名的操作了

  • moduleA.ts => 进行导出

    1
    2
    3
    4
    5
    export function study() {}

    export function play() {}

    export function sleep() {}
  • index.ts => 进行导入

    1
    import { study as s } from './moduleA'
    • 相当于在 moduleA 内导出了一个 study 方法
    • 但是在 index 文件内使用的时候, 使用 s 这个函数名

默认导出

  • 每个模块都会有一个默认导出, 使用 default 来进行

  • 需要注意 : 一个模块只能有一个默认导出

  • moduleA.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const utils = {
    study() {},

    play() {},

    sleep() {}
    }

    export default utils
    • 这是在 moduleA 内以默认导出形式导出了 utils 这个对象
  • index.ts

    1
    import XhlUtils from './moduleA'
    • 这里在 index 内的 XhlUtils 得到的就是 moduleA 文件内导出的 utils 对象

模块化兼容

  • 之前的都是 JS 的模块化语法, 我们相当于复习或者回顾
  • 现在这一块是 TS 对模块化的增强了, 各位小伙伴要注意学习了哦

descript

前言 :

我们刚才说的其实都是 ES6 Module 的语法规范

但是我们直到, 在 JS 的发展进程中, 我们并不是只有这一个模块化语法规范

我们还有 AMD / CommonJS / UMD 等模块化语法规范

各自有各自的导入导出语法规范

但是在这几个语法内有一个共同的规范, 就是 exports 变量的出现

exports 变量

在 CommonJS 和 AMD 内都有 exports 这个对象

就是这两个模块化规范的默认导出, 相当于 ES6 模块化规范内的 export default

虽然大家的语法是相似, 并且意义也差不多

但是 ES6 模块化语法, 并不能兼容 CommonJS 和 AMD 语法规范

所以在 TS 内对 ES6 的模块化语法进行了一些扩展

可以在编译的时候, 更好的支持 CommonJS 的语法

exports =

  • 如果你只是使用 TS 进行前端开发, 不需要考虑模块化语法的兼容, 那么你不需要了解这个

  • 只有当你需要兼容 CommonJS 和 AMD 语法的时候

  • 那么我们的 export default 就不能用了

  • index.ts => 不考虑兼容性

    1
    2
    3
    class Person {}

    export default Person
  • index.ts => 考虑兼容

    1
    2
    3
    class Person {}

    export = Person

import = require(‘’)

  • 为了支持兼容, 我们的导出语法变了, 那么我们的导入语法也要变化
  • index.ts => 不考虑兼容
    1
    import Person from './xxx'
  • index.ts => 考虑兼容
    1
    import Person = require('./xxx')

编译结果

  • 如果你按照兼容方式书写的导出内容, 会被编译成一下几个情况(看看得了)

    • SimpleModule

      1
      import Person = require('./xxx')
    • AMD( requireJS )

      1
      2
      3
      define(['require', 'exports', './xxx'], function (require, exports, mod_1) {
      console.log(mod_1)
      })
    • CommonJS( node )

      1
      const Person = require('./xxx')
    • UMD

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      ;(function (factory) {
      if (typeof module === 'object' && typeof module.exports === 'object') {
      let v = factory(require, exports)
      if (v !== undefined) module.exports = v
      } else if (typeof define === 'function' && define.amd) {
      define(['require', 'exports', './xxx'], factory)
      }
      })(function (require, exports) {
      let Person = require('./xxx')

      console.log(Person)
      })
    • System SimpleModule

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      System.register(['./xxx'], function (exports) {
      let Person

      return {
      setters: [
      function (param) {
      Person = param
      }
      ],

      execute: function () {
      console.log(Person)
      }
      }
      })
    • Native ES6 Module

      1
      import Person from './xxx'

命名空间

前言 :

最早的 TS 里面, 是没有命名空间的概念的, 就是模块化的概念

分成了两种, 内部模块 和 外部模块

从 TS1.5 开始, 内部模块 改名为 命名空间, 所以其实 命名空间 就是 内部模块

那么我们如何他们呢, 其实主要还是从内部封装的内容上来区分

命名空间(内部模块) : 主要用于组织代码, 避免命名冲突

外部模块(模块) : 侧重代码的封装复用, 一个模块内可能包含多个命名空间

例子

  • 我们在开发的过程中, 如果一个 ts 文件没有书写模块化语法

  • 那么会默认这个文件内所有定义的变量都是挂载在全局上的

  • 那么两个文件内就不能出现重复变量名, 不然会出现问题

  • a.ts

    1
    2
    3
    const num = 100

    const str = 'hello world'
  • b.ts

    1
    const num = 200
  • 看似没有关系的两个文件, 只是都写了 num 变量

  • 但是因为没有模块化语法, 这两个就都是挂载在全局的变量

descript

  • 这个时候, 我们就可以把这些内容放在命名空间( 内部模块 )内来解决这个问题

命名空间

  • 语法 :

    1
    2
    3
    namespace 命名 {
    /* ... */
    }
  • 在命名空间内可以定义变量使用, 也可以向外暴露内容

  • 在命名空间内向外暴露内容也是使用 export 关键字

    1
    2
    3
    4
    5
    6
    7
    namespace GoodsHandler {
    const current = 1
    const pagenum = 10
    const info = []

    export const list = info.slice((current - 1) * pagenum, current * pagenum)
    }
    • 这样一来, 我们所有的内容就放在了这个叫做 GoodsHandler 的命名空间内
    • 其中三个变量别的地方用不了, 只能在当前命名空间内使用
    • 向外暴露了一个叫做 list 的内容

引入命名空间

  • 当我们需要使用一个命名空间的时候, 我们可以使用三斜线(///) 指令
  • 注意 : 三斜线(///) 指令必须要要用在文件的最开始
1
2
3
/// <reference path="./goodsHandler.ts" />

console.log(GoodsHandler.list)

模块和命名空间

  • 当我们使用 模块 和 命名空间 组织我们的代码以后
  • 我们的向外暴露的内容越来越多, 也会考虑到文件太多的问题
  • 所以有的时候我们可以把命名空间和模块放在一起使用
  • 也就是在一个模块内, 可以导出多个 命名空间
  • 分清作用和概念
    • 命名空间: 主要为了解决变量命名冲突
    • 模块: 组织代码, 复用
1
2
3
4
5
6
export namespace GoodsHandler {
/* ... */
}
export namespace ListHandler {
/* ... */
}