ES2015 (ES6) で覚えておきたい構文:import・export

JavaScript でもようやく別ファイルの Import ができるようになった。それが exportimport 構文だ。

これまでのやり方

これまでの JavaScript では、複数のファイルにまたがって1つのモジュール (オブジェクト) を操作しようとすると、どうしてもグローバル変数を利用し、読み込み順を制御する必要があった。つまりこういうことだ。

/** script1.js : module クラスを作る **/
var module = {};
module.add = function(x, y) {
  return x + y;
};

/** script2.js : module クラスを使う **/
var result = module.add(1, 2);
console.log(result);

このような script1.jsscript2.js という2つのファイルがあった時、module はグローバル変数にしておかないと script2.js の中で参照できない。また、この2つの JS ファイルは HTML 側で以下の順で読み込まないといけない。

<!-- 変数「module」を宣言している script1.js を先に書かないとエラーになる -->
<script src="script1.js"></script>
<script src="script2.js"></script>

script2.js の中では、変数 module がどこで宣言されていて、どんなメソッドがあるのかなどが分からない。作る人が「そうだったはずだ」と見なしながら作るしかなかった。

それを解決するのが、ES6 の exportimport 構文というわけだ。

export 構文

export 構文によって、ある1つの JS ファイルから、1つ以上の変数を出力できる。export を書かなければ import もできないので、export するクラス内で使用している他のクラスなんかは export しないでおくことで、クラスの可視範囲を狭めることができる。Java でいう private なオブジェクト、といえるだろうか。

Default Exports

基本的には、export default という構文で、1つの JS ファイルから1つだけのモジュール (オブジェクト) をエクスポートする使い方が多い。これを Default Exports と呼ぶ。

/** script1.js : 無名の Function を Export する **/
export default function() {
  console.log('Export Default!');
}

/** script2.js : script1.js を Import して使う **/
// 適当な名前「myFunc」を付ける。ファイル名は拡張子「.js」不要
import myFunc from './script1';
myFunc();

クラスの受け渡しも同様。Function も Class も、export する側で付けた名前を import 側で守る必要はない。

/** script1.js : MyCalcClass という class を Export する **/
export default class MyCalcClass {
  add(x, y) {
    return x + y;
  }
}

/** script2.js : script1.js を Import して使う **/
// 適当な名前「CalcService」を付ける
import CalcService from './script1';
// class をインスタンス化する
const calcSrv = new CalcService();
// MyCalcClass#add() を使う
const result = calcSrv.add(1, 2);
console.log(result);

このように、1つの JS ファイルから1つだけ何らかの変数を出力して使うのが、export default 構文だ。

1ファイルから複数のモジュールを export default したらどうなるの?

1ファイルにつき1モジュールに限り、export default と書いて良い、となっているが、2つ以上書いたらどうなるのか、というと。

ES6 の基本的な仕様ではエラーとして扱われる。しかし、Babel でトランスパイルした場合は、後から export default と書いたモジュールの方が後勝ちで有効になり、エラーとは扱われない。Babel のトランスパイル方針が若干緩いので、意図しない不具合が起こらないよう注意したい。

Named Exports

export default ではなく、単なる export を使うとどうなるのかというと、1つの JS ファイルから複数のモジュールを Export できる。これを Named Exports と呼ぶ。

「Named Export」、つまり「名前付きエクスポート」ということだが、これは export する側の視点の話になる。export default の場合は無名関数や無名クラスを Export できたが、複数のモジュールをエクスポートする時は名前を付けておかないといけない。

/** script1.js : 2つの Function を Export する **/
export function myFunc1() {
  console.log('myFunc1');
}
export function myFunc2() {
  alert('myFunc2');
}

/** script2.js : script1.js を Import して使う **/
// 「分割代入」で myFunc1 と myFunc2 を読み込む
import { myFunc1, myFunc2 } from './script1';
myFunc1();
myFunc2();

読み込み時に「分割代入」と呼ばれる ES6 の構文を利用している。これについてはまた別途話をするが、分割代入なので Import 時に別名にすることもできる。

/** script2.js **/
// 「分割代入」で myFunc1 と myFunc2 をそれぞれ X・Y という別名にする
import { myFunc1 as X, myFunc2 as Y } from './script1';
x();  // myFunc1() が実行できる
Y();  // myFunc2() が実行できる

また、以下のようにアスタリスクでまとめて全読み込みすることもできる。

/** script2.js **/
// 「* as 変数名」でまとめて読み込む
import * as myLib from './script1';
// 「myLib」内に「myFunc1()」と「myFunc2()」が格納された状態になる
myLib.myFunc1();
myLib.myFunc2();

Named Exports を使い始めるとインポート時にゴチャゴチャするので、可能な限り Default Exports で済ませるようにしたい。

混在させることもできる

Default Exports と Named Exports を1ファイル内に混在させることもできる。

/** script1.js **/
// Default Exports
export default class MyClass {
  add(x, y) {
    return x + y;
  }
}
// Named Exports で2つ
export const HOGE_CONST = 'abc';
export function myFunc() {
  console.log('myFunc');
}

/** script2.js : 全て Import する **/
import MyClass, { HOGE_CONST, myFunc } from 'script1';

// それぞれ別名を付けても良い
import MyCalcService, { HOGE_CONST as HOGE, myFunc as fnc } from 'script1';

// Named Exports は「* as」にしても良い
import MyClass, * as MyLib from 'script1';
console.log( MyLib.HOGE_CONST );
MyLib.myFunc();

このように、Default Exports・Named Exports 両方の書き方をマージするような import 文を書けば良い。


基本的にはこんな感じ。exportimport 便利です。