TypeScript(十二)模块

news2024/9/25 15:23:30

目录

引言

d.ts声明文件

declare关键字

全局声明

全局声明方式

全局声明一般用作

函数声明

在.ts中使用declare

外部模块(文件模块)

模块关键字module

声明模块

模块声明方式

模块通配符

模块导出

模块嵌套

模块的作用域

模块别名

内部模块(命名空间)

命名空间 OR 模块?

global关键字

总结

参考文章


引言

本文收录于TypeScript知识总结系列文章,欢迎指正! 

将体量大的程序拆分成多个小的,功能独立的模块是开发中不可或缺的一环,开发复杂程序的核心之一就是让其变得不复杂。模块化开发可以提高代码的可维护性、可重用性、可扩展性和可测试性,从而提高了开发效率和代码质量,TypeScript沿用了JS的模块概念,在之前文章中我介绍过Node环境下的两种类型兼容,顺带提了一下目前常用的模块导入导出方式:Commonjs和ES Module,这两种方式在TS中被称为是外部模块,除此之外TS还包含了内部模块和全局模块,本文将逐一介绍

d.ts声明文件

在编译后的JS文件的同一级常能看到.d.ts后缀的声明文件,其作用是描述代码中已经存在的类型信息或为其提供类型声明。举个例子,使用第三方库时可能会找不到对应的类型信息,于是TS提供了声明文件这个概念,它使开发者拥有对库进行描述的能力,达到静态类型提示或者TS检查的目的,声明文件编译后不会生成任何js代码。

一般声明文件内部是不包含可执行语句的,只有类型或者变量的声明。在开发时通常会在项目根目录中新建一个像global.d.ts(名字自取)的文件用于描述全局的类型,变量,函数,类等等

declare关键字

declare是描述TS文件之外信息的一种机制,它的作用是告诉TS某个类型或变量已经存在,我们可以使用它声明全局变量、函数、类、接口、类型别名、类的属性或方法以及后面会介绍的模块与命名空间

全局声明

通常在global.d.ts文件中使用declare关键字进行全局声明,以便目录下所有文件都能直接访问

全局声明方式

  • declare var 名称: 变量
  • declare const / let 名称: ES6变量
  • declare function 名称: 方法
  • declare class 名称: 类
  • declare enum 名称: 枚举
  • declare module 名称: 模块
  • declare namespace 名称: 命名空间
  • declare interface 名称: 接口
  • declare type 名称: 类型别名

全局声明一般用作

  • 描述全局变量或类型
  • 描述第三方库的类型
  • 描述全局模块

举个例子,在项目根目录新建global.d.ts用于变量类型的全局声明,接着修改tsconfig中配置include为["global.d.ts", "src"],在项目任意目录新建index.ts

// global.d.ts
declare interface IAnimal {
    name: string
    age?: number
}

declare let animal: IAnimal
// src/index.ts
animal = {
    name: "阿黄"
}

可以看到index.ts文件中animal的类型是global.d.ts中声明的变量,其二者产生了关联

 

tips:声明文件(d.ts)中的所有类型(类型别名和接口除外)及变量都要使用declare定义,或者使用export将其导出,否则会抛出以下错误:.d.ts 文件中的顶级声明必须以 "declare" 或 "export" 修饰符开头。

此外,使用类型别名和接口定义的类型可以不需要声明,直接在全局访问

declare type str = string
// 相当于type str = string

函数声明

参照上面的定义方式,我们可以在声明文件中声明一个函数,然后再使用前对函数进行实现或重载

// global.d.ts
declare function add(a: number, b: number): number;

// src/index.ts
function add(a: number, b: number) {
    return a + b
}
console.log(add(1, 2));// 3

// src/main.ts
function add(a: number, b: number, c: string) {
    return a + b + c
}
console.log(add(1, 2, "3"));// 33

对函数声明进行重载

在.ts中使用declare

我们在介绍属性装饰器的时候曾用到了declare关键字,当时并没有具体说明使用它的原因,这里咱们详细分析一下,首先贴出一段类似的代码

class Animal {
    name?: string;
}

在ES2022及以后类中定义的属性会在编译后保留在类中,就像

class Animal {
    name;
}

而在.ts文件中使用declare只会被当成是类型或者变量的定义,最后编译在声明文件.d.ts中,不会编译在.js文件中,就像下面这个类

// index.ts
declare class Animal {
    name?: string;
}

// index.js
// 空文件

// index.d.ts
declare class Animal {
    name?: string;
}

通过declare这个特点,我们可以在类中属性或者方法定义时使用declare关键字将其指定为声明类型的变量,不会出现在.js中,有效的解决之前的问题

// index.ts
class Animal {
   declare name?: string;
}

// index.js
class Animal {}

外部模块(文件模块)

在TS中模块既可以以单个文件的形式存在,这与JS相同,通过export和import两个关键字进行导出导入,对应的介绍可以参照这篇文章的ESM部分,也可以使用module关键字定义模块。

与JS稍有不同,TS中包含了接口和类型别名,我们同样可以通过export type 类型名 导出对应类型别名,如

// src/main.ts
export type IAnimal = {
    name: string
    color?: string
}

// src/index.ts
import { IAnimal } from './main'
const animal: IAnimal = {
    name: "阿黄",
};

tips:在一个.d.ts文件中使用export关键字会使这个文件成为一个模块(这点很重要,一个声明文件(d.ts)不是全局声明文件(只使用declare声明类型)就得是文件模块(使用export等关键字导出)),比如我们把上面的global文件和index改成下面代码

// global.d.ts
type IAnimal = {
    name: string
    color?: string
}
export {}

// index.ts
const animal: IAnimal = {
    name: "阿黄",
};

此时直接使用全局的IAnimal就会抛错

必须使用export将IAnimal导出并使用import导入该模块中的类型

模块关键字module

声明模块

除了上面的使用方式外,我们可以使用module关键字在一个文件中定义多个模块,如

// global.d.ts
declare module 'global_type' {
    export type IAnimal = {
        name: string
    }
    export type ICat = {
        name: string
    }
}
declare module 'global_type1' {
    export type IDog = {
        name: string
    }
}

// index.ts
import type { IAnimal, ICat } from "global_type"
import type { IDog } from 'global_type1'
const animal: IAnimal = {
    name: "阿黄",
};
const dog: IDog = animal
const cat: ICat = animal

每一个使用module定义的内容是一个模块

模块声明方式

TS支持CommonJS和ESM两种模块系统,使得声明模块有两种写法,分别是使用字符串和变量名

CommonJS的写法遵循匹配文件的相对或绝对路径,通常模块名作为字符串字面量,该方法不支持导出模块,只允许使用declare定义全局模块,并且使用时需要使用import导入

// global.d.ts 
declare module "global_type" {
    export type IAnimal = {
        name: string
    }
}

// src/index.ts
import * as global_type from "global_type"
const myObject: global_type.IAnimal = {}

ESM的写法和定义变量一样,使用变量名匹配标识符进行模块的导入,这种方式与定义命名空间(namespace)的效果一样,使用ESM定义的全局模块可以直接使用,不需要导入

// global.d.ts 
declare module global_type {
    export type IAnimal = {
        name?: string
    }
}

// src/index.ts
const myObject: global_type.IAnimal = {}

模块通配符

我们在webpack或者vite等工具中可能会看到类似下面的代码,这种写法是CommonJS模块系统独有的

declare module '*.type' {
    export type IDog = {
        name: string
    }
}

这段代码中使用了*.type通配符,匹配了所有.type结尾的模块,导入.type类型的文件就会有IDog这个类型

import type { IDog } from 'global_type.type'
const animal: IDog = {
    name: "阿黄",
};
const dog: IDog = animal

模块导出

使用module定义的模块遵循全局声明,同样可以使用export导出并在其他文件使用,这种方式是ESM模块系统独有的

// global.d.ts 
export module global_type {
    export type IAnimal = {
        name: string
    }
    export class Animal implements IAnimal {
        name: string
    }
}

// index.ts
import { global_type } from '../global'
const myObject: global_type.IAnimal = new global_type.Animal();

模块嵌套

模块嵌套可以应对更复杂的结构,避免命名冲突,全局污染,在模块中写模块不需要declare和export关键字,模块默认自带导出

// global.d.ts 
declare module global_type {
    export module IAnimalModule {
        export let animal: IAnimal
        export type IAnimal = {
            name: string
        }
    }
    module IDogModule {
        let dog: IDog
        type IDog = {
            name?: string
        }
    }
}
// src/index.ts
let animal: global_type.IAnimalModule.IAnimal = global_type.IAnimalModule.animal
let dog: global_type.IDogModule.IDog = global_type.IDogModule.dog

模块的作用域

在模块嵌套时,我们可以把 module { } 或者 namespace { } 的大括号中的作用域称为模块的作用域,作用域中的模块类型可以访问,此时如果在模块中使用 export {} 导出空对象的话,当前模块就会被视为文件模块(这和我们上面说到的全局文件转模块文件的tips类似),需要使用export导出局部类型、变量、模块,否则会默认不做导出操作,成为一个私有的模块:

// global.d.ts 
declare module global_type {
    module IAnimalModule { // 局部模块,只能在global_type中使用
        let animal: IAnimal
        type IAnimal = {
            name?: string
        }
    }
    export { }
}

// src/index.ts
let animal: global_type.IAnimalModule.IAnimal// “global_type”没有已导出的成员“IAnimalModule”

此时需要将模块中的模块手动导出,或加入到导出对象中

export module IAnimalModule {
    let animal: IAnimal
    type IAnimal = {
        name?: string
    }
}
// 或者
export { IAnimalModule }

模块别名

模块的别名是一种用来简化其访问的方式,可以使用import关键字来定义一个别名,然后用这个别名来代替原来的名称,比如

// global.d.ts 
declare module global_type {
    export type IAnimal = {
        name?: string
    }
    export class Animal implements IAnimal { }
}

// src/index.ts
import Ani = global_type.Animal
import IAni = global_type.IAnimal
const ami: IAni = new Ani()

内部模块(命名空间)

因为1.5版本前的命名空间(namespace)是TS提出的模块理念,而上面说到的模块是JS中的ES标准,TypeScript对二者做了区分,所以它被称为内部模块。它同样是模块化机制的一员,它的作用是将全局的变量,函数,类等等封装在一个空间内,防止命名污染,冲突。官方比较推荐使用namespace来代替module的ESM模块系统写法,所以在使用模块的变量写法时,TS会将其转换成namespace

 

还记得上面说的ESM模块系统的导出方式吗?我们把代码中的module关键字换成namespace,就大功告成了,命名空间拥有模块的变量写法的特性。

namespace global_type {
    export type IAnimal = {
        name: string
    }
    export class Animal implements IAnimal {
        name: string
    }
}
const myObject: global_type.IAnimal = new global_type.Animal();

我们把代码放到一个文件中解析一下编译后的JS文件

var global_type;
(function (global_type) {
    class Animal {
        name;
    }
    global_type.Animal = Animal;
})(global_type || (global_type = {}));
const myObject = new global_type.Animal();

可以看到,代码中使用iife产生了一个私有的作用域并且定义了一个空对象,将命名空间导出的变量放至对象中。

思考一个问题,一个命名空间必须通过一处代码块定义吗?

答案是否定的,类似函数重载,命名空间的定义允许声明合并,将同名的命名空间对象进行合并(后面的文章会说到,留个悬念)

命名空间 OR 模块?

模块适用于需要动态加载、封装和复用代码的场景,比如Node.js应用、Web应用、npm包等。模块可以利用模块加载器(如CommonJS/Require.js)或支持ES模块的运行时来管理依赖和导入导出。模块是ES6标准的一部分,是现代代码的推荐组织方式。

命名空间适用于需要在全局范围内定义变量、函数、类、接口等的场景,比如Web应用中使用<script>标签引入所有依赖的HTML页面。命名空间可以避免全局变量的命名冲突,但也会增加组件依赖的难度,尤其是在大型应用中。

global关键字

在TS中declare global关键字用于向全局作用域中添加类型或变量的声明

以我的理解,global的使用方式应该和module以及namespace类似,照葫芦画瓢,使用global { ... }作为一个代码块表示全局的作用域,在里面定义的类型以及变量应该是能够在任意地方取到的

// global.d.ts 
declare global {
    type IDog = {
        name: string
    }
    let animal: IDog
}

// src/index.ts
animal = {
    name: "阿黄"
}

然而事情并没有这么简单,它会提示:全局范围的扩大仅可直接嵌套在外部模块中或环境模块声明中。

在官方给出的d.ts模板中,代码最后一行做了一个导出的操作

这是为何?

实际上这是为了解决TS编译器的一个问题,上面我们说到,如果声明文件d.ts没有使用export导出或者没有使用declare定义类型、变量,那么编译器就会报错,而使用declare global和使用declare module、declare namespace、declare type、declare interface都不一样,这些类型或者模块的定义就是导出声明,不需要进行额外的export。所以在定义(说是拓展比较贴切)全局的global时会在代码底部导出空对象,来声明这个文件是有导出的

上面我们提到:使用export关键字会使声明文件成为一个模块;如果使用了declare global和export { },那么我们的全局变量和类型就不需要使用declare声明,可以直接写在declare global代码块中

下面是一个完整的示例

// global.d.ts 
declare global {
    type IDog = {
        name: string
    }
    let animal: IDog
}
export { }

// src/index.ts
animal = {
    name: "阿黄"
}

总结

文章写到这里也就结束了,本文简述了TS中模块的使用,针对声明文件,declare关键字,文件模块,命名空间以及全局global关键字这几个方面进行了介绍,同时提出了自己的看法及遇到的问题,希望能对你有帮助。

感谢你看到最后,如果觉得文章还不错的话,还请点赞收藏关注支持一下博主,对文章内容有任何问题还望在评论区留言或私信,感谢!

参考文章

命名空间和模块 - TypeScript 中文手册

TypeScript: Documentation - Global .d.ts

TypeScript: Documentation - Modules

typescript已经有模块系统了,为什么还需要namespace? - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/430507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习:皮尔逊相关系数——影评相关性分析案例

机器学习&#xff1a;皮尔逊相关系数——影评相关性分析案例 文章目录机器学习&#xff1a;皮尔逊相关系数——影评相关性分析案例:rocket:1、皮尔逊相关系数概念及公式:rocket:2、案例代码部分皮尔逊&#xff08;pearson&#xff09;相关系数、 斯皮尔曼&#xff08;spearman&…

vue2 和 vue3 共存

1.共存的前置条件 1.1 之前全局安装的 vue2 或者 vue3 的脚手架进行卸载&#xff0c;使用 npm命令卸载vue2 npm uninstall vue-cli -g 再使用 vue -V 查看版本出现以下提示为卸载完成 vue3卸载要使用命令npm uninstall vue/cli -g 1.2 安装cnpm 在命令行中输入 npm install -…

java面试题(SpringBoot)

SpringBoot 1.什么是SpringBoot&#xff1f;有什么优点&#xff1f; springboot是spring的子项目&#xff0c;是spring组件的一站式解决方案&#xff0c;简化了使用的难度&#xff0c;简省了配置 优点&#xff1a; 容易上手&#xff0c;提升开发效率内置web容器管理第三方工…

第六章 物理层

数据通信基础 数据通信基本概念 消息与信息&#xff1b;消息是信息的载体 消息&#xff1a;人类能够感知的描述信息&#xff1a;抽象概念&#xff0c;可以理解为消息中所包含的有意义的内容 通信&#xff1a;一点精确或近似地再生另一点的信息信号&#xff1a;传递信息的载体…

提高客户忠诚度的 5 种方法

提高客户忠诚度是成功经营企业的最重要因素之一。忠诚的客户也更有可能向他们的朋友和家人推荐您的业务&#xff0c;从而开辟更多的收入来源。所以您需要将您的战略重点放在建立作为企业主的客户忠诚度上。 提高客户忠诚度的 5种方法 1.优先考虑客户服务 作为企业应该积极主动…

工赋开发者社区 | MES/MOM数据采集系统需求分析和总体设计

随着社会市场需求的不断变化&#xff0c;传统生产车间内的数据采集技术无法跟上市场变化的需求&#xff0c;容易出现数据记录滞后、实时监控困难等问题&#xff0c;造成了车间的生产效率低、产品质量低。在全球信息化的时代背景下&#xff0c;企业内的信息化管理可有效促进经济…

企业管理系统CRM与ERP有何区别

随着企业不断发展壮大&#xff0c;越来越多的企业引进管理系统&#xff0c;助力自身生产管理。目前市面上比较火热的企业管理系统软件有OA、CRM和ERP系统&#xff0c;关于企业OA管理系统&#xff0c;前面我们已经给出了很多介绍。今天主要带大家了解一下企业管理系统CRM和ERP系…

一文弄懂Python中的 if __name__ == __main__

1. 引言 在Python相关代码中&#xff0c;我们经常会遇到如下代码段&#xff1a; # stuff if __name__ "__main__":# do stuff本文将尽可能使用简单的样例来解释这里发生了什么&#xff0c;以及需要使用if __name__"__main__"的情形。请注意&#xff0c;…

m3u8视频文件破解爬取教程(请尊重知识产权,切勿违法),该方法需要基础开发知识

// UserScript // name media-source-extract // namespace https://github.com/Momo707577045/media-source-extract // version 0.8.2 // description https://github.com/Momo707577045/media-source-extract 配套插件 // author Momo707577045 // i…

ACL综合实验

拓扑结构&#xff1a; 要求 1、PC1可以Telnet R1&#xff0c;但是不能pingR1 2、PC1可以ping R2&#xff0c;但是不能Telnet R2 3、PC2的所有要求与PC1相反 使用的设备&#xff1a;4台路由器、1台交换机 解决网络拓扑&#xff1a; 1、确定广播域的个数 2、分配网段 3、配置…

【2023 · CANN训练营第一季】:AscendCL应用开发深入讲解 基础知识介绍

昇腾&#xff08;Ascend&#xff09;CANN&#xff08;Compute Architecture for Neural Networks&#xff09;是华为推出的一款面向AI处理器的软件开发工具包&#xff0c;用于支持各种AI应用的开发和部署。它的深度神经网络应用开发流程可以分为以下几个主要步骤 AscendCL应用…

TS WIKI 个人知识库软件

TS WIKI 个人知识库软件(VER: 0.1) TS WIKI 目标是做一个可以本地化部署&#xff0c;小型的&#xff0c;适合个人或小型团队的知识管理软件。 T(iny) S(mall) WIKI 软件界面 TS WIKI 功能说明 1、简化的软件依赖和安装 依赖的软件最简化。软件只使用 SQLITE 数据库&#xf…

【Java闭关修炼】SpringMVC-HttpMeaasgeConverter

【Java闭关修炼】SpringMVC-HttpMeaasgeConverter概述RequestBodyRequestEntity使用原生servlet响应浏览器ResponseBody概述 浏览器发送到服务器&#xff0c;将请求报文转换为java对象 服务器响应浏览器&#xff0c;将java对象转换成响应报文 RequestBody 将请求体中的请求报…

Flink系列-6、Flink DataSet的Transformation

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 大数据系列文章目录 官方网址&#xff1a;https://flink.apache.org/ 学习资料&#xff1a;https://flink-learning.org.cn/ 目录Flink 算子Ma…

C生万物 | 火眼辨析指针数组与数组指针

本文我们来讲讲C语言中的易混淆的指针数组与数组指针✒ 文章目录 一、指针数组1、概念明细2、数组地址偏移量与指针偏移量3、指针变量与数组名的置换【✔】4、实例讲解① 指针数组存放地址② 指针数组存放数组 二、数组指针1、数组指针的定义2、&数组名VS数组名3、数组指针…

VL822- GEN2 10Gbps简述及运用(USB3.1 HUB芯片)

VL822是一颗USB3.1 GEN2&#xff08;10Gbps&#xff09;的HUB芯片&#xff0c;有三种封装分别是QFN88(10x10x0.85 mm);QFN76&#xff08;9x9x0.85 mm &#xff09;;QFN56(7x7x0.85 mm)。这三种封装在USB数据协议上都是一样&#xff0c;但各个封装都有自己的实际应用特点。 VL8…

ODrive运放电路的分析和替代方案

SimpleFOC、ODrive和VESC教程链接汇总&#xff1a;请点击   一、电流采样范围的计算 这是一个典型的Lowside电流采样运放电路&#xff0c;下图&#xff1a; 实际运用中会增加阻容做滤波处理。&#xff08;SP1-SN1&#xff09;就是电流在R5采样电阻上产生的压降&#xff0c…

模型融合和预测结果融合

模型融合和预测结果融合 文章目录模型融合和预测结果融合1. 模型融合提升技术1. Bagging 方法和随机森林2. Boosting 方法2. 预测结果融合策略1. Voting2. 软投票代码示例&#xff1a;3. Averaging 和 Ranking4. Blending5. Stacking3. 其他提升方法1. 模型融合提升技术 模型融…

一个Java线程的线生(线生 vs 人生)

java线程的使用 1. Java多线程概述 下面我们看下Java的多线程 1.1 java天生就是多线程的 一个Java程序从main()方法开始执行&#xff0c;然后按照既定的代码逻辑执行&#xff0c;看似没有其他线程参与&#xff0c;但实际上Java程序天生就是多线程程序&#xff0c;因为执行main…

设计模式 -- 建造者模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…