TypeScriptにおける「モジュールシステム」の総合ガイド(例付き)
On 10月 16, 2021 by adminモジュール標準
EcmaScriptは、JavaScriptにおけるモジュールのインポート方法を標準化しており、この標準はimport
とexport
という二つのキーワードで展開されています。
このチュートリアルでは、TypeScript のモジュールシステム (EcmaScript 標準にほぼ類似) のセマンティクスと、import
および export
キーワードがどのように機能するかを見ていくことにします。 import
キーワードを使用して他のモジュール・ファイルでモジュールをインポートする場合、インポートするファイルはインポートしたモジュールからアクセスしたい値を指定できます。
// program.ts
import { A } from 'path/to/values';console.log( A ); // "Apple"
上記の例では、program.ts
ファイルで values.ts
をインポートし、A
エクスポートしたメンバーを抽出することにしています。 これは、values.ts
が何らかの形で値A
をエクスポートしなければならないことを意味します。 A
を公開する方法は複数ある。
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A, B, C, D };
有効な識別子(名前)を持つ、アクセス可能な TypeScript の値であれば、すべてエクスポートすることが可能である。 この例では、変数 A
(定数でもよい)、クラス B
、などをエクスポートしている。 export {}
構文を使用すると、必要な数の値をエクスポートでき、誰でも import { A, B }
構文を使用してそれらをインポートすることができます。 エクスポートされた値をすべてインポートする必要はありません。
値が宣言された場所にエクスポートする別の方法があります。 また、値を初期化する必要はなく、ファイルの下の方で初期化することができます。 これは、以前のものと比べてはるかに良い方法です。
どちらの場合も、エクスポートしたメンバーを使用する必要がある場合は、同じファイル内でアクセスできます。 値に export
キーワードがあっても、モジュール ファイル内での動作は変わりません。
デフォルトのエクスポート
これまで、import { A } from 'path/to/values'
ここで A
は values.ts
のエクスポート メンバーであるなど、エクスポート メンバーの名前を提供することによってのみインポートできる値をエクスポートしてきました。 これらは named exports と呼ばれます。
また、名前を指定せずにインポート可能な単一の値をエクスポートすることもできます。 この値は、export
キーワードとともに default
キーワードで識別する必要があります。
// values.ts
var A = "Apple"; // can also use `let` or `const`
class A {}
function A(){}
enum A{}export default A;
デフォルト・エクスポートとしてエクスポートできるのは単一の値のみで、そのため作業するための export default {...}
式はありません。 名前付きエクスポートとは異なり、function
および class
宣言以外の変数 (または定数) 宣言で値をデフォルトとしてエクスポートすることはできません。
// values.ts
export default var A = "Apple"; // ❌ invalid syntax
export default enum D{} // ❌ illegal: not a function or class
export default class B {} // ✅ legal
export default function C(){} // ✅ legal
しかしながら、デフォルト エクスポートとして値を直接エクスポートすることは可能です。 これは、任意の JavaScript 式をデフォルトのエクスポート値としてエクスポートできることを意味します。
// values.ts
export default "Hello"; // string
export default {}; // object
export default () => undefined; // function
export default function(){}; // function
export default class{}; // class
デフォルトのエクスポートにはエクスポート名がないため、インポート時に任意の名前を指定できます。 同じモジュールファイルに名前付きエクスポートとデフォルトエクスポートを持つことができます。 同様に、以下のように 1 つの import
宣言でデフォルト エクスポートと名前付きエクスポートの値をインポートできます。
import Apple, { B, C } from 'path/to/values';
上記の例では、Apple
は values.ts
からデフォルト エクスポートを収集するために使用した名前です。 使用しない場合は、import
宣言から{ B, C }
式またはApple
名を削除できます。
💡 名前付きエクスポートと同様に、
import
宣言が使用されている場合はデフォルトのエクスポート値をインポートすることは必須ではありません。 しかし、Apple
はimport
宣言のシグニチャで名前付きエクスポートの前になければなりません。
export {}
署名で as default
構文を使用して、他の名前付きエクスポートと一緒にデフォルトとして値をエクスポートすることも可能です。 これはエイリアシングと呼ばれ、次のセクションで説明します。
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A as default, B, C, D };
💡 私が
default
エクスポートを使用せず、推奨しない理由は、開発者の経験に関係しています。 私たちが学んだように、import
宣言ではモジュールのデフォルトのエクスポートメンバーに任意の名前を指定できます。 この値が複数のファイルでインポートされる場合、任意の名前で参照でき、同じモジュールの同じエクスポート メンバーに対して異なる名前を持つことは悪い DX.
Import And Export Alias
デフォルトのエクスポートの場合、import
宣言で任意の名前を使って値を参照することが可能です。 しかし、名前付きエクスポートの場合はそうではありません。 ただし、as
キーワードを使用すると、任意の名前で名前付きエクスポート値を参照できます。
// program.ts
import A, { B as Ball } from 'path/to/values';console.log( B ); // ❌ Error: Cannot find name 'B'.
console.log( Ball ); // ✅ legal
上記の例では、values.ts
から B
をインポートしていますが、これは Ball
に名前が変更されています。 したがって、program.ts
ファイルでは、B
の代わりにApple
を使用することになります。 エクスポート・メンバーのエイリアスを作成すると、元の名前はグローバル・スコープに存在しなくなるため、B
は program.ts
に存在しなくなります。
💡 すでに任意の名前を指定することができるので、デフォルトのエクスポート・メンバーをエイリアス化できませんが、
import { default as defaultValue, }
は使用できます。
また、as
キーワードを使用してエクスポート中に値のエイリアスを作成することもできます。
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A as default, B, C as Cat, D };
上記の例では、変数 A
をデフォルト エクスポートとして、関数 C
を Cat
としてエクスポートしています。 したがって、これらの値を values.ts
ファイルからインポートする人は、A
にはデフォルトのインポート構文を、C
には Cat
の値を使用しなければなりません。
すべての名前付きエクスポートのインポート
ファイルから * as
構文を使用して、すべての名前付きエクスポートをインポートすることができます。
// program.ts
import Apple, * as values from 'path/to/values';console.log( values.B );
console.log( values.C );
console.log( values.D );
ここで、values.ts
からのすべての名前付きエクスポートは、keys
がエクスポート メンバの名前、values
がエクスポート メンバのエクスポートされた値になるオブジェクトである values
の下に保存されます。 何かをインポートすると、インポート値への参照があり、export
構文で同じ値を使用することができます。 何も問題はありません。
// lib.ts
import A, { B, C as Cat, D } from 'path/to/values';export { D as default, A, B, Cat as C, D };
上の例では、values.ts
から lib.ts
内にいくつかの値をインポートし、好みに応じてエクスポートしています。 これは、これらのインポートを lib.ts
で使用し、別のファイルが lib.ts
からインポートするときにそれらの一部が必要な場合はエクスポートしたい場合に最適です。
しかし、最初にインポートしなくてもファイルから値をエクスポートする export ... from
ステートメントもあります。
// lib.ts// re-exports
export { P as Paper, Q } from 'path/to/values-1';
export { N, O as Orange } from 'path/to/other/values-2';// default export
export default "hello";class B {}
function C(){}// named exports
export { B, C as Cat };
上の例では、values-1.ts
と values-2.ts
の export メンバーの一部を lib.ts
ファイルからエクスポートしています (デフォルトのエクスポートは除く)。 export ... from
構文ではエイリアシングも使用できます。
再エクスポート構文は現在のファイルに影響を与えないため、上記のように通常のエクスポートをファイル内で行うことができます。 export
キーワードとは異なり、export ... from
構文は再エクスポートされたメンバーへのアクセスを許可しません。 したがって、lib.ts
ファイル内の P
や Orange
にはアクセスできません。
// lib.ts
export { P as Paper, Q } from 'path/to/values-1';
export { N, O as Orange } from 'path/to/other/values-2';console.log( P ); // ❌ Error: Cannot find name 'P'.
console.log( Orange ); // ❌ Error: Cannot find name 'Orange'.
export * as ... from
を使用してエクスポート中に名前を変更する機能がある限り、export * from
構文を使用してファイルからすべての名前付きエクスポートをエクスポートすることが可能です。
// lib.ts
export * from 'path/to/values-1';
export * as values2 from 'path/to/other/values-2';
上記の例から、values-1.ts
が P
と Q
をエクスポートした場合、誰でも lib.ts
から P
と Q
をインポートすることができます。 しかし、values-2.ts
の名前付きエクスポートはすべて lib.ts
の名前付きエクスポート values2
として再エクスポートされるので、次の構文を使ってインポートする必要があります。
// program.ts
import { P, Q, values2 } from 'path/to/lib';
通常の方法では、export ... from
構文を使ってデフォルトのエクスポートにアクセスすることができません。 しかし、default
キーワードを使用してデフォルト・エクスポートをエクスポートできます。
// lib.ts
export { default } from 'path/to/values-1';
export { default as Apple } from 'path/to/values-2';
上記の例では、lib.ts
のデフォルト・エクスポートは、values-2.ts
のデフォルト・エクスポート値になっています。 デフォルトのエクスポート値である values-2
は、export という名前の Apple
として lib.ts
からエクスポートされます。
副作用のあるインポート
エクスポートされるメンバーを指定せずにファイルをインポートすることを想像できますか。
import 'path/to/action';
上の例では、action.ts
をインポートしていますが、action.ts
によってエクスポートされるメンバーを指定したことになっています。 これは有効な構文ですが、まったく異なる結果を得るために使用されます。
import
キーワードを使用してファイルをインポートする場合、まずそのキーワードが実行され、その後すべてのエクスポート・メンバーがインポートに利用できるようになります。 したがって、以下の例では、PI
をインポートするファイルは 3.14
を floating-point
の数値として取得します。
export const PI = parseFloat( "3.14" ); // 3.14
同じ論理に従って、ファイルをインポートすることによって副作用を作成したい場合、完全にそれを行うことができます。 たとえば、いくつかのグローバル変数を初期化するファイルがある場合、そのファイルのエクスポートされたメンバーを指定せずにそれをインポートできます (そもそも何もエクスポートしないこともできます)。
TypeScript コンパイラーは、2 つ以上の .ts
ファイルを一緒にコンパイルして JavaScript バンドル ファイル (.js
) を作成することができます。 これは通常、エントリーの TypeScript ファイルを提供し、その依存関係ツリーを経由して行われます。
依存関係ツリーは、エントリー ファイルのすべての import
宣言 (依存関係) を見て、これらのインポートしたファイルの依存関係を追跡することによって構築されます。 コードが実行時に何らかの副作用を発生させる可能性のあるファイルをインクルードしたい場合は、import
構文でエクスポートされるメンバーを指定せずに、そのファイルをインポートする必要があります。 これは、エントリ・ファイルまたは依存関係ツリーの内部にある任意のファイルの内部で行うことができます。
コメントを残す