2023年1月18日

エクスポートおよびインポート

ExportとImportのディレクティブには複数の構文バリエーションがあります。

以前の記事では簡単な使用方法を見ましたが、今回はさらに多くの例を見ていきます。

宣言前のエクスポート

変数、関数、またはクラスの前に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 classexport 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 *

通常、次のようないくつかの波括弧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');

一見すると、「すべてインポートする」ことは素晴らしいことで、書くのは短く、なぜ必要なものを明示的にリストする必要があるのでしょうか?

まあ、いくつかの理由があります。

  1. インポートする内容を明示的にリストすると、名前が短くなります。say.sayHi()ではなくsayHi()です。
  2. インポートの明示的なリストはコード構造のより良い概要を示してくれます。何がどこで使われているか。コードのサポートとリファクタリングが容易になります。
インポートしすぎることを恐れないでください

webpackやその他のような、最新のビルドツールは、モジュールをまとめてバンドルし、それらを最適化してロードを高速化します。また、それらは未使用のインポートを削除します。

例えば、巨大なコードライブラリからimport * as libraryとしてインポートし、わずか数メソッドだけを使用する場合、未使用のインポートは最適化されたバンドルに含まれません

「as」としてインポート

異なる名前でインポートするためにasを使用することもできます。

例えば、簡潔さのためsayHihiというローカル変数にインポートし、sayByebyeとしてインポートします

// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';

hi('John'); // Hello, John!
bye('John'); // Bye, John!

「as」としてエクスポート

同様の構文がexportにも存在します。

関数をhibyeとしてエクスポートしましょう

// 📁 say.js
...
export {sayHi as hi, sayBye as bye};

これでhibyeは外部の人にとって公式名となり、インポートで使用されるようになります

// 📁 main.js
import * as say from './say.js';

say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!

デフォルトエクスポート

実際には、主に2種類のモジュールがあります。

  1. 上で述べたsay.jsのように関数のライブラリやパックを含むモジュール。
  2. 単一のエンティティを宣言するモジュール。例として、モジュールuser.jsclass Userのみをエクスポートします。

たいていの場合、2番目のアプローチが好まれます。つまり、あらゆる「もの」がそれ自身のモジュール内にあります。

当然、すべてが独自のモジュールを必要とするため、多くのファイルが必要になりますが、それはまったく問題ではありません。実際、ファイルが適切に名前付けられ、フォルダに構造化されている場合、コードナビゲーションが簡単になります。

モジュールは、「1つのモジュールごとの1つのもの」の方法をより良く見せるために、特別なexport default(「デフォルトエクスポート」)構文を提供します。

エクスポートするエンティティの前にexport defaultを置きます

// 📁 user.js
export default class User { // just add "default"
  constructor(name) {
    this.name = name;
  }
}

ファイルごとにexport defaultは1つしかありません。

...そして、中括弧なしでインポートします

// 📁 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 ...

技術的には、1つのモジュールにデフォルトと名前付きの両方のエクスポートがある可能性がありますが、実際にはそれらを混ぜる人はほとんどいません。モジュールには名前付きエクスポートかデフォルトのエクスポートのいずれかがあります。

ファイルごとにデフォルトエクスポートが最大1つしかないため、エクスポートされたエンティティには名前がない可能性があります。

例えば、これらはすべて完全に有効なデフォルトエクスポートです

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は1つしかないため、中括弧なしの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が1つのメイン「デフォルト」のものと、いくつかの名前付きのもの(まれなケースですが、起こります)をエクスポートするとします

// 📁 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';
...

それでも、デフォルトのエクスポートは重大な欠点と考えるチームもあります。そのため、常に名前付きのエクスポートを使用することを好みます。エクスポートされるものが1つだけの場合でも、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 ... fromimport/exportの注目すべき違いは、再エクスポートされたモジュールが現在のファイルでは使用できないことです。したがって、auth/index.jsの上記の例では、再エクスポートされたlogin/logout関数は使用できません。

デフォルトのエクスポートの再エクスポート

デフォルトのエクスポートは再エクスポート時に別個に処理する必要があります。

export default class Userを使用してuser.jsがあり、それを再エクスポートしたいとします。

// 📁 user.js
export default class User {
  // ...
}

これには2つの問題が発生する可能性があります。

  1. export User from './user.js'は機能しません。構文エラーが発生します。

    デフォルトのエクスポートを再エクスポートするには、上記の例のようにexport {default as User}と記述する必要があります。

  2. export * from './user.js'は名前付きエクスポートのみを再エクスポートし、デフォルトのエクスポートは無視します。

    名前付きエクスポートとデフォルトのエクスポートの両方を再エクスポートしたい場合は、2つのステートメントが必要です。

    export * from './user.js'; // to re-export named exports
    export {default} from './user.js'; // to re-export the default export

デフォルトのエクスポートを再エクスポートすることのこのような奇妙さは、一部の開発者がデフォルトのエクスポートを嫌って名前付きのエクスポートを好む理由の1つです。

要約

この記事と以前の記事で取り上げたexportのすべてのタイプを以下に示します。

それらを読み、それらが何を意味するかを思い出すことで自分自身をチェックできます。

  • クラス/関数/…の宣言の前
    • export [default] クラス/関数/変数 ...
  • スタンドアロンエクスポート
    • 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
}

…しかし、条件付きで何かをインポートする、または適切なタイミングでインポートする必要がある場合はどうでしょうか? 実際に必要なときにリクエストに応じてモジュールを読み込むなど。

次の記事では、動的インポートについて説明します。

チュートリアルマップ

コメント

コメントする前にこれを読んでください。
  • 改善策の提案がある場合は、コメントする代わりにGitHubの問題を送信するか、プルリクエストを送信してください。
  • 記事内の何かを理解できない場合は、詳しく説明してください。
  • コードを数文字挿入するには、<code>タグを使用し、数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックス(plnkrjsbincodepen…)を使用します。