最近用 TypeScript 寫 npm 包,各種模塊、命名空間、全局定義等等擾得我睡不著覺。我苦心研究,總結了幾個比較冷門的,國內貌似基本上找不到資料的導入導出用法,順便在其中又插入一些不那麼冷門的用法,於是本篇文章來了。 ...
前言
最近用 TypeScript 寫 npm 包,各種模塊、命名空間、全局定義等等擾得我睡不著覺。
我便苦心研究,總結了幾個比較冷門的,國內貌似基本上找不到資料的導入導出用法,順便在其中又插入一些不那麼冷門的用法,於是本篇文章來了。
因為一開始也沒想做成大全,可能之後還會繼續更新吧。
目錄
導入模塊
導入模塊中的東西相信大家都不陌生。唯一需要註意的便是預設導出與“星號”導出的區別。
import * as Mod from './mod';
// 類似於
const Mod = require('./mod');
import ModDef from './mod';
// 類似於
const { default: ModDef } = require('./mod');
import ModDef, { a, b } from './mod';
// 類似於
const {
default: ModDef,
a, b
} = require('./mod');
在模塊中導出
在模塊中導出東西相信大家也不陌生,不過這裡還是詳細講解一下。
在模塊中導出東西有很多種方法。導出總共可分為 4 類:
命名導出
命名導出有兩種方法,一種是聲明著導出
export namespace A { }
export function b() { }
export class C { }
export const d = 123;
export let { e } = { e: 'hh' };
一種是聲明後導出
namespace A { }
function b() { }
class C { }
const d = 123;
let { e } = { e: 'hh' };
export { A, b, C, d, e };
聲明後導出比聲明著導出更靈活,能合併,也能重命名
namespace A { }
export { A };
function b() { }
export { b as c };
class C { }
export { C as B };
const d = 123;
let { e } = { e: 'hh' };
export { d, e };
命名導出編譯成 Common JS 後類似這樣
exports.xxx = xxx;
需要註意的是其他人無法修改任何你導出的東西。即使是使用 let
聲明也一樣
/* mod.ts */
export let a = 123;
/* others.ts */
import { a } from './mod';
a = 321; // 報錯:ts(2632)
不過對於上面的代碼,你可以隨便修改所導出的 a
。因為其他人每次讀取 a
時都會重新從你的導出對象上訪問一次 a
屬性,不用擔心其他人無法接收到你的修改。具體可以查看編譯後的 JS 文件
/* others.ts */
import { a } from './mod';
const b = a + 123;
console.log(a);
/* others.js */
var mod_1 = require("./mod");
var b = mod_1.a + 123;
console.log(mod_1.a);
預設導出
預設導出可以理解為一種特殊的命名導出。
預設導出的名字是 default
。但是你不能搞個名字叫 default
的變數然後導出,你必須得用 export default
或者在導出時重命名
export let default = 123; // 報錯:ts(1389)
export default 123; // 正確
export let a = 123;
export { a as default }; // 正確
星號導入搭配預設導出,可以達到預設導出即為星號導出的效果
/* mod.ts */
import * as Self from './mod';
export default Self;
/* others.ts */
import * as Mod from './mod';
import ModDef from './mod';
console.log(Mod === ModDef); // true
星號導出
星號導出可以導出其他模塊里的東西。一般有兩種用法。
第一種是全導出到自己裡頭,就像
export * from './xxx'
具體效果是 xxx
中導出的東西,也可以通過你訪問到。
/* xxx.ts */
export let a = { hh: 'hh' };
/* mod.ts */
export * from './xxx.ts';
/* others.ts */
import { a } from './xxx';
import { a } from './mod';
console.log(a === a); // true
第二種是掛到自己模塊下麵,就像
export * as xxx from './xxx';
// 等價於
import * as xxx from './xxx';
export { xxx };
導出分配
導出分配就是把 Common JS 的導出搬到了 TS 中。寫法也差不多
export = 'hh';
// 相當於
module.export = 'hh';
導出分配也可以指定預設導出,只需要有 default
屬性就可以
/* mod.ts */
export = { default: 123 };
/* others.ts */
import mod from './mod';
console.log(mod); // 123
需要註意的是採用了導出分配後便不能再使用其他導出方法。
導入命名空間
雖然現在命名空間相較於模塊並不是特別常用,但它還是有比較完整的導入導出功能的。
導入命名空間中的東西很簡單,像這樣
import xxx = Space.xxx;
不論你是在模塊中導入全局命名空間,還是在命名空間中導入其他命名空間,都是適用的。
import Err = globalThis.Error;
throw Err('hh');
namespace A {
import Process = NodeJS.Process;
let proce: Process;
}
較為可惜的是命名空間貌似沒有星號導入,也不支持解構導入。
在命名空間中導出
在一般 TS 中,命名空間只有兩種方法導出。
第一種方法是聲明著導出,類似於模塊
namespace A {
export const a = 123;
}
第二種方法是導入著導出,可以用來導出其他命名空間的東西
namespace A {
export import Err = globalThis.Error;
}
而對於不一般的 TS ——也就是類型聲明中,命名空間還可以採用像模塊一樣的導出對象
declare namespace A {
const a = 123;
const b = 'hh';
export { a, b };
}
使用全局定義
全局定義一般有三種:
-
內置在 TS 中的全局定義。比如
setTimeout
、Error
等。
對於這種全局定義,直接拿來用就可以了。 -
位於環境模塊中的全局定義。比如
NodeJS.Process
等。包括位於
node_modules/@types
文件夾中的自動引入的環境模塊,都可以通過三斜杠註釋來引入。你可以通過
path
直接指定文件路徑/// <reference path="./types.d.ts" />
-
位於模塊中的全局定義。
這種全局定義只需要引入一下模塊,表示你已經運行此模塊,即可
import '@babel/core';
或者你也可以通過三斜杠註釋,通過
types
指定模塊/// <reference types="@babel/core" />
需要註意的是,不論你採用
import
還是三斜杠註釋,甚至只是在類型中使用了一個typeof import('xxx')
,只要你在一個 TS 文件中引入了這個模塊所定義的全局類型,那這個類型就會永遠存在下去,污染你的globalThis
。
唯一在不污染全局域的情況下運行模塊的方法是使用import()
函數動態引入,但這樣子你也拿不到你需要的類型。
進行全局定義
進行全局定義一般有三種方法。
第一種是直接寫環境模塊。不帶任何 import
和 export
一般就會讓編譯器把這當成一個環境模塊。所以,如果你需要防止一個 TS 文件變成環境模塊導致類型泄露的話,你可以加一個安全無公害的 export { };
。
第二種是在模塊中定義,只需要把類型定義寫到 declare global
裡頭就行
declare global {
const a = 123;
let b: {};
}
a; // 123
b; // {}
第三種是通過合併 globalThis
命名空間來定義,好處是可以使用命名空間的“導入著導出”方法,將模塊或者其他命名空間的局部變數變成全局變數
import _Mod from './mod';
declare global {
namespace globalThis {
const a = 123;
export import Mod = _Mod;
}
}
a; // 123
Mod; // typeof import('./mod')