深入理解TypeScript的模块系统 | 青训营笔记

227 阅读6分钟

在ES2015出现之前,JavaScript还没有原生的模块系统(ES Module),只能借助第三方的RequireJS和SystemJS来使用模块功能。

模块本质上就是一个JS/TS文件,类似一个沙盒环境,使用import语句引入其他模块的API和值,export语句暴露自己的API和值。

TypeScript原生支持ES Module的语法,可以在.ts文件中使用import和export语句。默认情况下,TypeScript仅支持引入.ts和.d.ts文件模块,可以通过修改allowJS配置选项,使TypeScirpt支持.js文件(这个在详解tsconfig.json文件文章中介绍过)。

一.导入模块的方式 // example.ts import "../program"; import "package"; 1 2 3 上面的代码,就是要介绍的两种导入模块的方式。

路径:通过模块(文件)路径导入模块,可以是相对路径,也可以是绝对路径 模块名称(包名):通过模块名称(包名称)导入模块。 第一种方式比较简单,typescript将会根据我们指定的文件路径寻找文件(program.ts或者program.d.ts),上面的例子中,typescript会在当前文件的父级目录中优先寻找program.ts,如果program.ts没有找到,再寻找program.d.ts,如果找到则导入找到的模块。

第二种方式,通过包名引入,情况比较复杂,接下来将重点介绍。

二.模块定位策略 上面的第二个例子import "package";,通过模块名导入模块。当typescirpt处理这种导入语句时,它会使用以下两个模块定位策略中的一个。

Classic模块定位策略:为了与旧版本兼容才发明的一种策略。 Node模块定位策略: 默认策略。 TypeScript的模块定位策略是可配置的,可以通过tsc的--moduleResolution选项配置,也可以通过moduleResolution编译选项进行配置(tsc与编译选项在详解tsconfig.json文件详细讨论过)。

Classic模块定位策略 使用Classic模块定位策略,TypeScirpt首先在当前文件的同级目录查找与模块名相同的.ts文件或者.d.ts文件,如果没有找到,则向上一级目录查找,知道找到或者抵达磁盘根目录也未找到,如果找到则映引入找到的模块。

以上面的例子import "package";为例。假设example.ts文件路径为D:\program\src\example.ts。首先在example.ts文件的同级目录D:\program\src\寻找package.ts和package.ds文件,如果没找到继续在父级目录D:\program\中寻找package.ts和package.ds文件,如果没找到继续向上在D盘根目录D:\中寻找,如果找到则引入模块。

Node模块定位策略 Node模块定位策略(Node Module Resolution)是TypeScript的默认定位策略,也是最常用的模块定位策略。

只要是JS程序员,肯定知道项目都有一个node_modules文件夹,里面存放了一些公共包,供项目使用。

Node模块定位策略对于非Node.js程序员来说,可能有点复杂。与Classic模块定位策略相同,typescript也会向上遍历文件夹,寻找模块,但是也有一些不一样。

以下面的例子为例:

// D:\code\src\example.ts import '../program'; import 'package'; 1 2 3 第一个import语句,使用相对路径导入模块,这种情况下,TypeScript会在父级目录寻找program.ts和program.d.ts文件,如果没有找到,TypeScript会检查父级目录下有没有一个program文件夹,如果D:\code\src\program\存在,如果这个D:\code\src\program\目录下有package.json文件存在,然后它会导入package.json中types和typings字段指定的路径的文件。

如果D:\code\src\program\目录下不存在package.json文件,或者它缺少types字段,然后它会检查该目录下是否有index.ts和index.d.ts文件,如果找到则导入模块,否则TypeScript报错。

整个过程如下。

  1. ../program.ts

  2. ../program.d.ts

  3. ../program/package.json -> [[types]] -> file path

  4. ../program/index.ts

  5. ../program/index.d.ts

  6. Error: Cannot find module 1 2 3 4 5 6 7 对于第二个import语句,使用包名导入模块,情况变得更复杂了。首先,TypeScript会寻找当前目录下D:\code\src的node_modules文件夹,然后按照与路径导入模块方式相同的方式查找模块。

如果它没有找到合适的文件,它会向上(父级目录,向外遍历)使用相同的逻辑寻找。整个过程如下。

Import statement: import 'package'; Import Location: D:\code\src\example.ts

  1. D:/code/src/node_modules/package.ts

  2. D:/code/src/node_modules/package.d.ts

  3. D:/code/src/node_modules/package/package.json -> [[types]] ->

  4. D:/code/src/node_modules/package/index.ts

  5. D:/code/src/node_modules/package/index.d.ts

  6. D:/code/node_modules/package.ts

  7. D:/code/node_modules/package.d.ts

  8. D:/code/node_modules/package/package.json -> [[types]] ->

  9. D:/code/node_modules/package/index.ts

  10. D:/code/node_modules/package/index.d.ts

  11. D:/node_modules/package.ts

  12. D:/node_modules/package.d.ts

  13. D:/node_modules/package/package.json -> [[types]] ->

  14. D:/node_modules/package/index.ts

  15. D:/node_modules/package/index.d.ts

  16. Error: Cannot find module TS中的函数与可调用注解 为参数添加注解 在TS中,我们可以为函数的参数添加注解。 为函数返回值添加注解 类似于为函数的参数添加注解,TS也支持为函数的返回值添加注解。(但是一般我们不需要为函数的返回值添加注解,因为TS编译器会自动推断)。 如果我们想要定义一个不返回值的函数,那么可以将函数返回值的类型定义为void。 可选参数 我们可以使用 ? 将某个参数定义为可选的参数。 我们也可以为参数提供一个默认值。(在参数类型声明的后面使用 = initalValue)。 函数重载 函数重载的作用 函数重载通过定义多个同名函数,但是形参不同,而实现,当传入参数不同时,函数将执行不同的逻辑。 声明函数 在没有提供函数实现的情况下,有2种方法可以声明函数。 type LongHand = { (a: number): number }

如果想使用函数重载,必须使用这种方法。(因为这种方法可以通过内联注解声明多个函数类型)。 type LongHand = (a: number) => number

这种方法可以用于Vue data域 中的函数类型的响应式数据的类型声明。 可调用 TS支持我们使用类型别名或接口来定义一个可被调用的类型注解。 interface returnString{ (): string } 这个接口定义了一个可调用注解,它表示一个返回值为 string 的函数。 可调用类型注解有3种情况 使用interface定义一个一个或多个可调用注解,当定义多个可调用注解时,可以使用函数重载。

interface Overload{
    (a: number): number,
    (a: string): string,
}

function stringOrNumber(foo: number): number
function stringOrNumber(foo: string): string

function stringOrNumber(a: any): any
{
    if(typeof a === 'number')
    {
        return a*a;
    }
    else if(typeof a === 'string')
    {
        return 'hello' + a;
    }
}

const func: Overload = stringOrNumber;
const str = func("TS")
const num = func(2)
console.log(str)
console.log(num)

TS允许使用箭头函数定义可调用注解,但是使用箭头函数将无法使用函数重载。

可实例化的可调用注解。

它在可调用注解的前面加上了 new 关键字,这表示我们需要使用 new 来调用这个可调用注解。 可调用注解的作用 可调用注解可以用于声明函数重载。

使用接口或类型别名定义多个可调用注解。

可调用注解可以用于快速声明函数类型或在Vue的data中声明一个响应式的函数数据

使用箭头函数可以实现。 可调用注解可以用于声明构造函数。

使用可实例化的可调用注解实现。