TypeScript 模块那些事儿
哈喽,大家好,我是 SuperYing。今天我们来聊聊 TypeScript 模块那些事儿。
关于术语的一点说明: TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与 ECMAScript 2015 里的术语保持一致,(也就是说
module X {
相当于现在推荐的写法namespace X {
)。
什么是模块
TypeScript 沿用了 ES6 的模块概念。模块只能在其自身的作用域内执行,而不是在全局作用域里。模块内的变量,函数,类等可以通过 export
导出,也可以通过 import
导入所需的其他模块内容。
模块更形象的叫法应该是“文件模块”,任何包含 import 或 export 的文件,都会被识别为模块。相反,若一个文件中没有 import
或 export
,则该文件内容全局可见。
用法
- 导出
- 导出声明
// 导出变量 export const foo = '123' // 导出类型 export type FooType = { foo: string } // 导出函数 export function bar() { console.log(123) }
- 导出语句
// 导出变量 const foo = '123' // 导出类型 type FooType = { foo: string } // 导出函数 function bar() { console.log(123) } export { foo, FooType, bar }
- 重新导出,可以通过
as
重命名变量并导出
const foo = '123' export { foo as renamedFoo }
- 默认导出
每个模块仅能有一个默认导出,使用
default
关键字标记。
export default '123' export default function Foo() {} export default class Foo {}
- 导入
- 导入模块的一个变量或类型
import { foo } from './foo'
- 重命名导入的变量或类型
import { foo as renamedFoo } from './foo'
- 导入整个模块,使用
* as
指定一个对象,导入模块的所有输出值都赋值给该对象
import * as Foo from './foo'
- 只导入模块,有些模块内部可能会设置某些全局状态,供其他模块使用,在模块没有任何导出或者用户不关心其导出内容时,可以使用如下方式导入:
import 'core-js'; // 一个普通的 polyfill 库
- 默认导入
import defaultFoo from './foo'
查找策略
模块查找策略相关的 TypeScript 配置: 开启
moduleResolution: node
选项,启用 Node 模式;如果使用了module: commonjs
则moduleResolution: node
会默认开启。
模块查找场景主要分为以下两种:
- 相对路径模块(以 . 开头,例如 ./foo, ../foo 等)
- 其他动态查找模块(如 vue, react, reactDOM 等)
相对路径模块
仅按照相对路径规则查找即可:
- 如果文件
bar.ts
中含有import * as foo from './foo'
,那么foo
文件必须与bar.ts
文件存在于相同的文件夹下。 - 如果文件
bar.ts
中含有import * as foo from '../foo'
,那么foo
文件所存在的地方必须是bar.ts
的上一级目录。 - 如果文件
bar.ts
中含有import * as foo from '../someFolder/foo'
,那么foo
文件所在的文件夹someFolder
必须与bar.ts
文件所在文件夹在相同的目录下。
其他动态查找模块
若导入路径不是相对路径,则模块查找与 Node 模块解析策略相似。
-
当你使用
import * as foo from 'foo'
,将会按如下顺序查找:./node_modules/foo
../node_modules/foo
../../node_modules/foo
- 直到系统的根目录
-
当你使用
import * as foo from 'something/foo'
,将会按照如下顺序查找:./node_modules/something/foo
../node_modules/something/foo
../../node_modules/something/foo
- 直到系统的根目录
导入模块解析
仍然以 import * as foo from 'foo'
为例:
- 若 foo 是一个文件,匹配。
- 否则,若 foo 是一个文件夹,且存在 foo/index.ts, 匹配。
- 否则,若 foo 是一个文件夹,且存在 package.json 文件,在该文件中指定了 types 属性且对应的文件存在,匹配。
- 否则,若 foo 是一个文件夹,且存在 package.json 文件,在该文件中指定了 main 属性且对应的文件存在,匹配。
模块编译
通过设置 tsconfig 编译选项 module
的值,可以把 TavaScript 模块编译成不同 JavsScript 模块类型。
"CommonJS"
:NodeJs 模块。"AMD"
:Require.js 模块。"System"
:SystemJs 模块。"UMD"
:兼容多个模块加载器,或者不使用模块加载器(全局变量)。"ES6"
或"ES2015"
:ES6 模块。 目前比较常用的是CommonJS
,UMD
和ES6
,大部分框架或库会同时编译为这三种模式,如 Element-Plus。
*.d.ts
我们在使用 TypeScript 开发的时候,有时会用到非 TypeScript 类库,它们没有自己的类型,我们在引入的时候,往往会收到 TypeScript 的报错信息,类似于 ‘无法解析对应的模块’。
那么如何搞定这种类库的类型呢,这时候就需要我们来手动声明类库暴露出来的 API 了。这类声明通常在 .d.ts
文件中定义。(这里使用 module 关键字,并且名称使用引号包裹)
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
有时候我们只是想快速使用某个类库模块,并不想去一一声明其 API 类型。可以使用如下简写形式:
declare module "url"
简写形式下所有导出的类型都是 any
。
好啦!以上便是「 TypeScript 模块 」的全部内容,感谢阅读。
欢迎各路大佬讨论、批评、指正,共同进步才是硬道理!
转载自:https://juejin.cn/post/7093058241063551007