匯出和匯入指令有數種語法變體。
在上一篇文章中,我們看到了簡單的用法,現在讓我們探討更多範例。
在宣告之前匯出
我們可以在宣告之前加上 export
來標示任何宣告為已匯出,無論是變數、函式或類別。
例如,以下所有匯出都是有效的
// export an array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
export class User {
constructor(name) {
this.name = name;
}
}
請注意,在類別或函式之前加上 export
並不會讓它成為 函式表達式。它仍然是函式宣告,只是已匯出。
大多數 JavaScript 風格指南不建議在函式和類別宣告後加上分號。
這就是為什麼在 export class
和 export function
的結尾不需要分號
export function sayHi(user) {
alert(`Hello, ${user}!`);
} // no ; at the end
除了宣告之外的匯出
此外,我們可以將 export
分開
我們在此先宣告,然後再匯出
// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
export {sayHi, sayBye}; // a list of exported variables
…或者,技術上來說,我們也可以將 export
放在函式的上方
匯入 *
通常,我們會在花括號中列出要匯入的內容 import {...}
,如下所示
// 📁 main.js
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
但是,如果要匯入的內容很多,我們可以使用 import * as <obj>
將所有內容匯入為物件,例如
// 📁 main.js
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
乍看之下,「匯入所有內容」似乎很酷,寫起來很簡短,為什麼我們還要明確列出需要匯入的內容?
嗯,有幾個原因。
- 明確列出要匯入的內容會產生較短的名稱:
sayHi()
取代say.sayHi()
。 - 明確的匯入清單可以更清楚地瞭解程式碼結構:什麼地方使用了什麼內容。這讓程式碼支援和重構變得更容易。
匯入「as」
我們也可以使用 as
以不同的名稱匯入。
例如,我們將 sayHi
匯入到區域變數 hi
中以簡化,並將 sayBye
匯入為 bye
// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';
hi('John'); // Hello, John!
bye('John'); // Bye, John!
匯出「as」
export
有類似的語法。
我們將函式匯出為 hi
和 bye
// 📁 say.js
...
export {sayHi as hi, sayBye as bye};
現在 hi
和 bye
是對外正式的名稱,可用於匯入
// 📁 main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
匯出預設值
在實務上,模組主要有兩種。
- 包含函式套件的模組,例如上述的
say.js
。 - 宣告單一實體的模組,例如模組
user.js
只匯出class User
。
通常,第二種方法較為理想,這樣每個「事物」都存在於自己的模組中。
當然,這需要很多檔案,因為每個事物都需要自己的模組,但這完全不是問題。事實上,如果檔案命名得當並結構化到資料夾中,程式碼導覽會變得更容易。
模組提供特殊的 export default
(「預設匯出」)語法,讓「每個模組一個事物」的方式看起來更佳。
在要匯出的實體之前放入 export default
// 📁 user.js
export default class User { // just add "default"
constructor(name) {
this.name = name;
}
}
每個檔案只能有一個 export default
。
…然後在沒有花括號的情況下匯入
// 📁 main.js
import User from './user.js'; // not {User}, just User
new User('John');
不使用大括號的匯入看起來比較美觀。在開始使用模組時,一個常見的錯誤是完全忘記大括號。因此,請記住,import
需要大括號才能進行命名匯出,但不需要大括號進行預設匯出。
命名匯出 | 預設匯出 |
---|---|
export class User {...} |
export default class User {...} |
import {User} from ... |
import User from ... |
技術上來說,我們可以在單一模組中同時擁有預設和命名匯出,但在實務上,人們通常不會將它們混在一起。一個模組只能有命名匯出或預設匯出。
由於每個檔案最多只能有一個預設匯出,因此匯出的實體可能沒有名稱。
例如,以下都是完全有效的預設匯出
export default class { // no class name
constructor() { ... }
}
export default function(user) { // no function name
alert(`Hello, ${user}!`);
}
// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
不給名稱是可以的,因為每個檔案只有一個 export default
,因此不使用大括號的 import
知道要匯入什麼。
沒有 default
的話,這樣的匯出會產生錯誤
export class { // Error! (non-default export needs a name)
constructor() {}
}
「預設」名稱
在某些情況下,default
關鍵字用於參照預設匯出。
例如,要將函式與其定義分開匯出
function sayHi(user) {
alert(`Hello, ${user}!`);
}
// same as if we added "export default" before the function
export {sayHi as default};
或者,另一個情況,假設模組 user.js
匯出一個主要的「預設」項目,以及一些命名的項目(這種情況很少見,但確實會發生)
// 📁 user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
以下是匯入預設匯出和命名匯出的方法
// 📁 main.js
import {default as User, sayHi} from './user.js';
new User('John');
最後,如果將所有內容 *
作為物件匯入,則 default
屬性就是預設匯出
// 📁 main.js
import * as user from './user.js';
let User = user.default; // the default export
new User('John');
反對預設匯出的話
命名匯出是明確的。它們明確命名它們匯入的內容,因此我們可以從它們那裡獲得該資訊;這是一件好事。
命名匯出強迫我們使用完全正確的名稱來匯入
import {User} from './user.js';
// import {MyUser} won't work, the name must be {User}
…而對於預設匯出,我們在匯入時總是選擇名稱
import User from './user.js'; // works
import MyUser from './user.js'; // works too
// could be import Anything... and it'll still work
因此,團隊成員可能會使用不同的名稱來匯入相同的事物,這並不好。
通常,為了避免這種情況並保持程式碼一致性,有一個規則是匯入的變數應對應到檔案名稱,例如
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...
儘管如此,有些團隊認為這是預設匯出的嚴重缺點。因此,他們偏好總是使用命名匯出。即使只匯出一個項目,它仍然會在沒有 default
的情況下以名稱匯出。
這也讓重新匯出(見下文)變得更容易一些。
重新匯出
「重新匯出」語法 export ... from ...
允許匯入項目並立即匯出它們(可能使用另一個名稱),如下所示
export {sayHi} from './say.js'; // re-export sayHi
export {default as User} from './user.js'; // re-export default
為什麼需要這樣做?讓我們看看一個實際的用例。
想像一下,我們正在編寫一個「套件」:一個包含許多模組的資料夾,其中一些功能已匯出到外部(像 NPM 之類的工具允許我們發布和分發此類套件,但我們不必使用它們),而許多模組只是「輔助程式」,用於其他套件模組的內部使用。
檔案結構可能如下
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
我們希望透過單一進入點公開套件功能。
換句話說,想要使用我們套件的人員,應該只從「主檔案」auth/index.js
匯入。
如下
import {login, logout} from 'auth/index.js'
「主檔案」auth/index.js
匯出我們希望在套件中提供的全部功能。
這個想法是,外部人員(使用我們套件的其他程式設計師)不應該干擾其內部結構,在我們的套件資料夾中搜尋檔案。我們只在 auth/index.js
中匯出必要的項目,並將其餘部分隱藏起來,避免他人窺探。
由於實際匯出的功能分散在整個套件中,我們可以將其匯入 auth/index.js
並從中匯出
// 📁 auth/index.js
// import login/logout and immediately export them
import {login, logout} from './helpers.js';
export {login, logout};
// import default as User and export it
import User from './user.js';
export {User};
...
現在我們套件的使用者可以 import {login} from "auth/index.js"
。
語法 export ... from ...
只是此類匯入匯出的較短表示法
// 📁 auth/index.js
// re-export login/logout
export {login, logout} from './helpers.js';
// re-export the default export as User
export {default as User} from './user.js';
...
export ... from
與 import/export
的顯著差異在於,重新匯出的模組在目前的檔案中不可用。因此在上述 auth/index.js
的範例中,我們無法使用重新匯出的 login/logout
函式。
重新匯出預設匯出
重新匯出時,預設匯出需要分開處理。
假設我們有 user.js
,其中包含 export default class User
,並希望重新匯出
// 📁 user.js
export default class User {
// ...
}
我們可能會遇到兩個問題
-
export User from './user.js'
無法運作。這會導致語法錯誤。要重新匯出預設匯出,我們必須寫入
export {default as User}
,如上述範例所示。 -
export * from './user.js'
僅重新匯出命名匯出,但會忽略預設匯出。如果我們希望重新匯出命名匯出和預設匯出,則需要兩個陳述式
export * from './user.js'; // to re-export named exports export {default} from './user.js'; // to re-export the default export
重新匯出預設匯出的這些奇特之處,是某些開發人員不喜歡預設匯出而偏好命名匯出的原因之一。
摘要
以下是我們在本文和前幾篇文章中涵蓋的所有 export
類型。
您可以透過閱讀並回想它們的意義來自行檢查
- 在類別/函式/… 宣告之前
export [default] class/function/variable ...
- 獨立匯出
export {x [as y], ...}
.
- 重新匯出
export {x [as y], ...} from "module"
export * from "module"
(不會重新匯出預設)。export {default [as y]} from "module"
(重新匯出預設)。
匯入
- 匯入命名匯出
import {x [as y], ...} from "module"
- 匯入預設匯出
import x from "module"
import {default as x} from "module"
- 匯入所有
import * as obj from "module"
- 匯入模組(其程式碼會執行),但不要將任何匯出指派給變數
import "module"
我們可以在指令碼的頂端或底部放置 import/export
陳述式,這並不重要。
因此,技術上來說,這個程式碼是正確的
sayHi();
// ...
import {sayHi} from './say.js'; // import at the end of the file
實際上,匯入通常在檔案的開頭,但這只是為了更方便。
請注意,如果 {...}
內部有 import/export 陳述式,它們將無法運作。
條件式匯入,像這樣,將無法運作
if (something) {
import {sayHi} from "./say.js"; // Error: import must be at top level
}
…但如果我們真的需要有條件地匯入某些東西呢?或是在正確的時間匯入?例如,在需要時根據要求載入模組?
我們將在下一篇文章中看到動態匯入。
留言
<code>
標籤,對於多行 - 將它們包裝在<pre>
標籤中,對於超過 10 行 - 使用沙盒 (plnkr,jsbin,codepen…)