夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • TypeScript基础

  • TypeScript进阶

    • 声明合并
    • declare声明
    • TS的(.d.ts)文件
    • 命名空间namespace
    • 理解TS模块
      • 导入导出语法
      • 在其他 JS 库中使用模块
        • 环境模块
        • 通配符 * 模块声明
        • UMD 模块
      • 模块结构化规范
        • 导入导出规范
        • 通过重导出进行模块扩展
        • 不要在模块中使用命名空间
      • 模块解析
  • TS 从零实现 axios

  • 《TypeScript》
  • TypeScript进阶
夜猫子
2023-03-09
目录

理解TS模块

# 深入理解 TS 模块

模块有自己的作用域,除非进行某种形式的导出,否则,其中的变量,函数,类等都是对外不可见的。相应的,如果要在模块外使用其导出的成员,则需要进行相

应的导入。模块的相互导入需要使用 模块加载器 ,模块加载器在执行模块之前,会先定位并执行该模块的依赖项。JS中主要使用Node.js的CommonJs模块

加载器和Web应用程序中的AMD模块的RequireJs加载器。TS延用了ES2015模块化方案,任何包含了顶层的import或export语句的文件,便是一个模块;

相反,没有在顶层包含这些语句的则是脚本,其内容在全局作用域中可见。

# 导入导出语法

export:规定模块对外接口

export default Test // 默认导出(导入时可指定模块任意名称,无需知晓内部真实名称)

export const name = "Bruce" // 单独导出

export { age, name, sex }(推荐) // 按需导出

export { name as newName } // 改名导出

export = // commonjs 导出模块 exports/module.exports导出模块, 针对这类模块的声明文件,需要使用export =导出
    
export type nameType=string // 明确是值还是类型
1
2
3
4
5
6
7
8
9
10
11

import:导入模块内部功能

import Test from "test" // 默认导入

import * as Test from "test" // 整体导入

import { age, name, sex } from "test" // 按需导入

import { name as newName } from "test" // 改名导入

import "test" // 自执导入

import Test, { name } from "test" // 复合导入

import type {nameType} from  "test" // 引入类型(无法作为值来使用)
1
2
3
4
5
6
7
8
9
10
11
12
13

# 在其他 JS 库中使用模块

有些库不是用 TS 写的,我们声明它们需要暴露出的 API 来描述库的形状。未定义实现的声明称为环境 ambient ,通常写在 .d.ts 中。

# 环境模块

在 node.js 中,许多的任务都是通过各个模块来完成的,我们可以通过顶层的声明在各自的 .d.ts 文件中定义每一个模块,但一个更方便的方式是将它们都写在一个更大的 .d.ts 文件中。

我们可以通过环境命名空间来对它们进行改造,都是使用 module 关键字以及带引号的模块名,这些模块名用于后续的导入。

简化的node.d.ts

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;
}

declare module "@/utils/imgTools" { // 本地文件
  export function pathToBase64(path: string): Promise<any>;
  export function base64ToPath(base64: string): Promise<any>;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

随后便可以使用三斜杠指令/// <reference path="node.d.ts"/>来引入,并使用import url = require("url");或import * as URL from "url"来加载相应模块。

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

# 通配符 * 模块声明

SystemJs和AMD等,允许导入非JavaScript的内容。这些内容通常会使用前缀或后缀来表示相关的语义。因此,使用通配符模块声明可以使你很方便地涵盖这些情况。

declare module "*!text" {
  const content: string;
  export default content;
}
// Some do it the other way around.
declare module "json!*" {
  const value: any;
  export default value;
}
1
2
3
4
5
6
7
8
9

上面的例子中使用了*!,其中*为通配符,表示任意字符内容,!在此处用来分隔语义。之后,我们可以导入任何匹配*!text或json!*的内容

import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);
1
2
3

# UMD 模块

有些库在设计时兼顾了多种模块加载器,或作为全局变量以在没有模块加载器时使用。这些就是我们熟知的UMD模块。这些库既可以通过某种形式的导入来使用,也可以直接通过其暴露的全局变量来使用

如下使用了export as namespace mathLib 来暴露一个全局变量mathLib,在脚本中(注意,不是模块中)通过该变量可以访问模块成员。

// mathLib.d.ts
// 导出一个函数成员
export function isPrime(x: number): boolean;
// 定义一个全局变量mathLib
export as namespace mathLib;
  
// app1.ts
import {isPrime} from "math-lib";
isPrime(3);
  
// app2.ts
// 不导入,直接通过全局变量mathLib来使用
mathLib.isPrime(3);
1
2
3
4
5
6
7
8
9
10
11
12
13

# 模块结构化规范

# 导入导出规范

1.导出单个时,使用默认导出 export default。

2.如果导出多个成员,尽量放在顶层导出。

export class SomeType {
  /* ... */
}
export function someFunc() {
  /* ... */
}
1
2
3
4
5
6

3.如果导入少量的成员,应该显式的列出导入的名称。

import { SomeType, someFunc } from "./MyThings";
let x = new SomeType();
let y = someFunc();
1
2
3

4.导入大量的成员时,最好使用命名空间导入模式 import * as Name from "Module"

// largeModule.ts
export class Dog { ... }
export class Cat { ... }
export class Tree { ... }

// app.ts
import * as LM from "largeModule"
const Wangcai = new LM.Dog()
const Kitty = new LM.Cat()
const rose = new Tree()
1
2
3
4
5
6
7
8
9
10

# 通过重导出进行模块扩展

当我们需要对模块的功能进行扩展时,通常的做法是,不修改原有的模块的内容,而是重新导出一个具有新的功能的实体,然后通过 as 重命名为原模块名。

// car.ts
export class Car  {
  constructor(brand:string) {
    this.carname = brand;
  }
  present(){
      return 'I have a' + this.carname
  }
}
1
2
3
4
5
6
7
8
9
// ProgrammerCar.ts
import {Car} from  './car'
class ProgrammerCar extends Calculate { // 通过 extends 关键字来实现继承
    constructor(brand:string,mod:string){
        super()
        this.modal = mod;
    }
    show(){
        return this.present() + ',it is a' + this.modal
    }
}

export { ProgrammerCar as Car }; // 导出继承后的新的实体类,并重命名为 car
1
2
3
4
5
6
7
8
9
10
11
12
13

# 不要在模块中使用命名空间

因为模块有它自己的作用域,只有导出的成员才具有对外的可见性。因此我们应该尽量避免在模块中使用命名空间。特别是以下两种情况:

1.模块唯一的顶层导出不能是命名空间:export namespace Foo {...} , 模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层。

2.多个文件导出了同名的命名空间:export namespace Foo {...} ,注意:同一模块下命名空间一致会合并,不同模块下同名命名空间不会合并。

# 模块解析

我们知道模块有两种导入方式,分别为 相对导入 和 非相对导入,

在我们导入模块时,根据模块路径的书写形式,分为 相对导入 和 非相对导入 。相对导入,顾名思义,就是使用相对路径来导入模块,包含./,../等表示相对路径的字符,如

  • import { getName } from "./Person";
  • import Person from "./Person";
  • import "./mod";

非相对导入,便是不包含./,../等表示路径的字符,如:

  • import { getName } from "Person";
  • import Person from "Person";
  • import "mod";

相对导入是相对于当前文件来解析,且不能解析为环境模块声明。我们自己的模块,应该使用相对导入。非相对导入基于baseUrl或者路径映射来解析,可以解析为环境模块声明。导入外部依赖时,应使用非相对导入。

编辑 (opens new window)
上次更新: 2023/8/22 19:22:36
命名空间namespace
简介

← 命名空间namespace 简介→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式