TypeScriptでのESModulesにおけるexportとexport defaultの使い方を徹底解説

TypeScriptはJavaScriptの型付きスーパーセットとして、モジュールの管理や構成において強力な機能を提供しています。ESModules(ECMAScriptモジュール)は、モジュールを効率的に管理し、コードを分割して再利用可能にするための標準化された仕組みです。この記事では、TypeScriptにおけるESModulesを使ったエクスポートの方法として、「名前付きエクスポート」と「デフォルトエクスポート」の基本的な使い方を紹介し、それぞれの違いと適切な使用シナリオについて詳しく解説します。

名前付きエクスポートとデフォルトエクスポートは、モジュール間でのデータのやり取りを行う際に不可欠な要素です。それぞれが持つ特性を理解し、使いこなすことで、コードの再利用性や保守性が向上します。まずは、それぞれの仕組みを順に見ていきましょう。

目次

ESModulesとは

ESModules(ECMAScript Modules)は、JavaScriptの標準的なモジュールシステムです。これにより、コードを個別のファイルやパッケージに分割し、他のファイルから簡単にインポートして再利用できるようになります。ESModulesは、従来の「CommonJS」や「AMD」などのモジュールシステムに代わる標準仕様として、JavaScriptの進化において重要な役割を果たしています。

ESModulesの特徴

ESModulesは、以下のような特徴を持っています。

1. 宣言的なインポートとエクスポート

ESModulesでは、importexportというキーワードを使って、他のモジュールから関数や変数、クラスをインポートし、現在のモジュールからそれらをエクスポートできます。この宣言的なスタイルにより、依存関係が明確に記述され、コードの可読性が向上します。

2. スコープの分離

モジュールはそれぞれ独自のスコープを持ち、グローバルスコープを汚染することなく、必要な部分だけを他のモジュールに公開します。これにより、名前の衝突や予期しない副作用を避けることができます。

3. ロードの遅延

ESModulesは、必要なタイミングでモジュールをロードできる遅延ロードをサポートしています。これにより、初期ロード時間の短縮やパフォーマンスの向上が可能になります。

ESModulesの導入により、JavaScriptとTypeScriptのコードはよりモジュール化され、メンテナンスが容易になります。次に、名前付きエクスポートについて詳しく説明します。

名前付きエクスポートの仕組み

名前付きエクスポートは、TypeScriptやJavaScriptのモジュールで、複数の値や関数、クラスなどをモジュールから個別にエクスポートする方法です。名前を指定してエクスポートすることで、他のモジュールからその名前を使って明確にインポートすることができます。これは、複数のエクスポートを必要とする場合や、特定の要素を選んでインポートしたい場合に非常に便利です。

名前付きエクスポートの基本

名前付きエクスポートでは、exportキーワードを使って、関数や変数、クラスを明示的にエクスポートします。1つのモジュールから複数のエクスポートが可能で、インポート側では必要なエクスポートだけをインポートできます。

// example.ts
export const myValue = 42;
export function myFunction() {
  console.log("Hello from myFunction!");
}
export class MyClass {
  greet() {
    console.log("Hello from MyClass!");
  }
}

上記のコードでは、myValuemyFunctionMyClassがそれぞれ名前付きエクスポートとしてエクスポートされています。

名前付きエクスポートのインポート方法

名前付きエクスポートをインポートする際は、import文を使い、波括弧 {} の中にエクスポートした名前を指定します。

// anotherFile.ts
import { myValue, myFunction, MyClass } from './example';

console.log(myValue); // 42
myFunction(); // "Hello from myFunction!"
const instance = new MyClass();
instance.greet(); // "Hello from MyClass!"

このように、名前付きエクスポートはインポート時に特定のエクスポートを選んで読み込むことができ、不要なコードの読み込みを避けることができます。

名前付きエクスポートの利点

名前付きエクスポートには、以下のような利点があります。

1. 柔軟なインポート

名前付きエクスポートでは、必要なものだけを選んでインポートできるため、モジュール内の全てのエクスポートを無駄なく使用することが可能です。

2. コードの可読性とメンテナンス性の向上

名前をつけてエクスポートすることで、モジュール間の依存関係が明確になり、コードの可読性が向上します。将来的に新しいエクスポートを追加する場合も、既存のコードに影響を与えにくくなります。

次は、デフォルトエクスポートについて解説します。

デフォルトエクスポートの仕組み

デフォルトエクスポートは、1つのモジュールから最も重要な値や関数、クラスをエクスポートする際に使用されます。名前付きエクスポートとは異なり、モジュールは1つだけデフォルトエクスポートを持つことができ、インポートする際には特定の名前を指定する必要はありません。デフォルトエクスポートは、モジュールの「メインの機能」や「メインのクラス」を提供する場面でよく使われます。

デフォルトエクスポートの基本

デフォルトエクスポートは、export default キーワードを使用して指定します。モジュール内で1つだけ使用可能で、インポートする際にはインポート元が自動的にこのデフォルトエクスポートを選択します。

// example.ts
export default function greet() {
  console.log("Hello from the default function!");
}

この場合、greet 関数がデフォルトエクスポートとしてエクスポートされており、他のファイルからこの関数をインポートする際には、特定の名前を指定しなくても利用できます。

デフォルトエクスポートのインポート方法

デフォルトエクスポートをインポートする際には、名前を自由に指定してインポートできます。波括弧 {} を使わず、単純に名前を指定するだけです。

// anotherFile.ts
import greetFunction from './example';

greetFunction(); // "Hello from the default function!"

この例では、greetFunction という任意の名前を使用してデフォルトエクスポートをインポートしています。インポート時の名前は自由に変更できるため、モジュールを利用する側のコードスタイルに合わせて使うことが可能です。

デフォルトエクスポートの利点

デフォルトエクスポートには、以下のような利点があります。

1. シンプルなインポート

デフォルトエクスポートを使うことで、インポートする際にエクスポート名を指定する手間が省け、シンプルなコードを実現できます。特に、モジュールが提供するメインの機能を強調したい場合に便利です。

2. インポート時の名前の自由度

インポート側でエクスポート名を自由に設定できるため、プロジェクトの命名規則に応じて柔軟に対応できます。例えば、複数の同様の機能を持つモジュールがある場合に、それぞれ異なる名前を付けて区別することができます。

次に、名前付きエクスポートとデフォルトエクスポートの違いと、その使い分けについて詳しく見ていきましょう。

名前付きエクスポートとデフォルトエクスポートの違い

名前付きエクスポートとデフォルトエクスポートは、TypeScriptやJavaScriptのモジュールシステムにおいて非常に重要な機能です。どちらもモジュール内の要素を他のモジュールで再利用するための手段ですが、それぞれ異なる使い方や特性を持っています。ここでは、両者の違いを詳しく比較し、それぞれの適切な使用シーンについて解説します。

1. エクスポートの数

名前付きエクスポート

名前付きエクスポートは、1つのモジュールから複数の要素をエクスポートすることが可能です。関数、変数、クラスなどを複数エクスポートし、それぞれを個別にインポートできます。モジュールが複数の関連機能を提供する場合に便利です。

export const myValue = 42;
export function myFunction() {
  console.log("Hello from myFunction");
}
export class MyClass {
  greet() {
    console.log("Hello from MyClass");
  }
}

デフォルトエクスポート

デフォルトエクスポートは、モジュールごとに1つしか存在できません。モジュール全体の「メインの機能」として位置付けられることが多く、他の要素と比べてそのモジュールの中心的な役割を果たすものに使用されます。

export default function mainFunction() {
  console.log("This is the main function");
}

2. インポートの方法

名前付きエクスポート

名前付きエクスポートは、インポートする際に波括弧 {} を使用し、エクスポートされた名前を指定します。必要なものだけをインポートできるため、無駄なコードの読み込みを避けられます。

import { myValue, myFunction } from './example';

デフォルトエクスポート

デフォルトエクスポートは、インポート時に任意の名前をつけてインポートでき、波括弧は不要です。名前の指定は自由なので、コード内で使いやすい名称を選ぶことができます。

import main from './example';

3. 用途の違い

名前付きエクスポートの用途

名前付きエクスポートは、複数の関連機能をエクスポートしたいときに使用します。例えば、ユーティリティ関数や定数など、モジュール内に複数の異なる要素が含まれている場合に便利です。また、モジュールの特定の部分だけを他のモジュールで利用することができ、柔軟な構成が可能です。

デフォルトエクスポートの用途

デフォルトエクスポートは、モジュールが1つの主要な機能やクラスを提供する場合に使用します。たとえば、1つのメイン関数やメインクラスがあり、それがそのモジュールの中核的な役割を果たす場合には、デフォルトエクスポートが適しています。シンプルで、他のモジュールにインポートしやすいことも特徴です。

4. 適切な使い分け方

両者の使い分けは、モジュールがどのような役割を持っているかによります。複数の機能をエクスポートし、使用する側で選択的に利用したい場合は名前付きエクスポートが適しています。一方で、モジュール全体が1つの中心的な機能を提供している場合には、デフォルトエクスポートを使う方が簡潔で扱いやすいです。

次に、それぞれのエクスポートを実際のコード例で確認していきましょう。

実際のコード例: 名前付きエクスポート

名前付きエクスポートは、モジュールから複数の要素を個別にエクスポートし、他のファイルでそれぞれを選択してインポートすることができます。ここでは、具体的なコード例を用いて、名前付きエクスポートの使い方を詳しく説明します。

名前付きエクスポートの基本例

以下の例では、変数、関数、クラスをそれぞれ名前付きエクスポートしています。

// utils.ts
export const pi = 3.14159;

export function calculateArea(radius: number): number {
  return pi * radius * radius;
}

export class Circle {
  constructor(public radius: number) {}

  getArea(): number {
    return calculateArea(this.radius);
  }
}

このコードでは、pi という定数、calculateArea という関数、そして Circle クラスが名前付きエクスポートされています。

名前付きエクスポートをインポートする例

上記の utils.ts から、特定のエクスポートだけを他のモジュールでインポートする方法を見てみましょう。

// main.ts
import { pi, calculateArea } from './utils';

console.log(`円周率: ${pi}`);
console.log(`半径5の円の面積: ${calculateArea(5)}`);

この main.ts では、picalculateArea だけをインポートして使用しています。名前付きエクスポートの利点は、不要なエクスポートをインポートしなくてもよい点にあります。Circle クラスは使わないので、ここではインポートしていません。

全ての名前付きエクスポートをまとめてインポートする方法

時には、モジュールから全てのエクスポートをまとめてインポートしたい場合もあります。そんなときは、import * as を使って、全ての名前付きエクスポートを1つのオブジェクトとしてインポートできます。

// main.ts
import * as Utils from './utils';

console.log(`円周率: ${Utils.pi}`);
console.log(`半径5の円の面積: ${Utils.calculateArea(5)}`);

const myCircle = new Utils.Circle(3);
console.log(`半径3の円の面積: ${myCircle.getArea()}`);

この例では、Utils オブジェクトに utils.ts からエクスポートされた全ての要素をまとめてインポートしています。この方法を使うと、各要素に対して1つずつ名前を指定する必要がなく、簡潔に記述できます。

名前付きエクスポートの利点

名前付きエクスポートは、モジュールの再利用性を高め、不要なコードを省くことができます。複数のエクスポートを持つモジュールにおいて、必要な機能だけを選択してインポートできるため、パフォーマンスや可読性の面でも優れています。また、全ての要素を1つのオブジェクトとしてインポートできるため、柔軟な使用方法が可能です。

次に、デフォルトエクスポートの実際のコード例を確認しましょう。

実際のコード例: デフォルトエクスポート

デフォルトエクスポートは、モジュールから最も重要な要素を1つだけエクスポートする際に使います。名前付きエクスポートと違い、デフォルトエクスポートは1つのモジュールで1回しか使用できませんが、インポート時には名前を自由に指定できるため、シンプルな使い方が可能です。ここでは、デフォルトエクスポートの具体的なコード例を見ていきます。

デフォルトエクスポートの基本例

次の例では、関数をデフォルトエクスポートしています。

// mathUtils.ts
export default function calculateCircumference(radius: number): number {
  return 2 * Math.PI * radius;
}

この場合、calculateCircumference 関数がモジュールのデフォルトエクスポートとしてエクスポートされています。デフォルトエクスポートは、モジュール内で1つしか存在できないため、この関数が mathUtils.ts モジュールのメイン機能として提供されていることが明確になります。

デフォルトエクスポートをインポートする例

デフォルトエクスポートをインポートする際は、波括弧 {} を使わずに、自由に名前をつけてインポートすることができます。

// main.ts
import calculateCircumference from './mathUtils';

console.log(`半径5の円の円周: ${calculateCircumference(5)}`);

このコードでは、calculateCircumference 関数をデフォルトエクスポートとしてインポートしています。main.ts ファイル内では関数名をそのまま利用していますが、インポート時に他の名前を付けても問題ありません。

// main.ts
import calcCirc from './mathUtils';

console.log(`半径10の円の円周: ${calcCirc(10)}`);

このように、デフォルトエクスポートはインポートする際に名前を柔軟に変更できる点が便利です。

クラスをデフォルトエクスポートする例

次に、クラスをデフォルトエクスポートする場合の例を見てみましょう。

// shapes.ts
export default class Rectangle {
  constructor(public width: number, public height: number) {}

  getArea(): number {
    return this.width * this.height;
  }
}

この場合、Rectangle クラスがデフォルトエクスポートされています。インポートする際も、同じく名前を自由に付けることができます。

// main.ts
import Rect from './shapes';

const myRectangle = new Rect(10, 20);
console.log(`長方形の面積: ${myRectangle.getArea()}`);

このように、デフォルトエクスポートはクラスにも適用でき、モジュールの中心的な機能を簡潔にインポートして使うことができます。

デフォルトエクスポートの利点

デフォルトエクスポートは、モジュールが1つの主要な機能を提供する場合に非常に適しています。インポート時に名前を自由に付けられるため、コードベースの命名規則に合わせて柔軟に対応できます。また、インポートがシンプルになるため、名前付きエクスポートよりも直感的に扱いやすい場面があります。

次は、名前付きエクスポートとデフォルトエクスポートを混在させて使用する方法を見ていきましょう。

名前付きエクスポートとデフォルトエクスポートの混在使用

名前付きエクスポートとデフォルトエクスポートは、同じモジュール内で一緒に使うことができます。これにより、モジュールの主要な機能をデフォルトエクスポートし、補助的な機能や他の関連機能を名前付きエクスポートとして提供することができます。この方法は、柔軟なモジュール設計を可能にし、モジュールの使い勝手を向上させます。

ここでは、名前付きエクスポートとデフォルトエクスポートを同時に使った具体的な例を見ていきましょう。

名前付きエクスポートとデフォルトエクスポートの基本例

次の例では、クラスをデフォルトエクスポートし、関連する関数を名前付きエクスポートとして提供しています。

// shapes.ts
export default class Rectangle {
  constructor(public width: number, public height: number) {}

  getArea(): number {
    return this.width * this.height;
  }
}

export function calculatePerimeter(width: number, height: number): number {
  return 2 * (width + height);
}

export const PI = 3.14159;

この例では、Rectangle クラスがデフォルトエクスポートとして定義されています。一方で、calculatePerimeter 関数と PI 変数は名前付きエクスポートとしてエクスポートされています。これにより、モジュールの中心的な機能(この場合は Rectangle クラス)をデフォルトエクスポートし、関連する補助的な機能を名前付きエクスポートで提供することが可能です。

混在するエクスポートのインポート方法

インポート側では、デフォルトエクスポートと名前付きエクスポートを組み合わせてインポートできます。

// main.ts
import Rectangle, { calculatePerimeter, PI } from './shapes';

const myRectangle = new Rectangle(10, 20);
console.log(`長方形の面積: ${myRectangle.getArea()}`);
console.log(`長方形の周囲: ${calculatePerimeter(10, 20)}`);
console.log(`円周率: ${PI}`);

ここでは、Rectangle クラスをデフォルトエクスポートとしてインポートし、calculatePerimeter 関数と PI 変数を名前付きエクスポートとしてインポートしています。デフォルトエクスポートは名前を自由に変更できるので、コードがシンプルに保たれ、モジュールの主要機能と補助機能を明確に分けて扱うことができます。

別名を使用したインポートの例

また、名前付きエクスポートはインポート時に別名(エイリアス)を付けることも可能です。これにより、異なる名前のエクスポートが衝突しないようにしたり、より読みやすい名前を使用することができます。

// main.ts
import Rect, { calculatePerimeter as calcPerim, PI as PiValue } from './shapes';

const anotherRectangle = new Rect(5, 15);
console.log(`長方形の面積: ${anotherRectangle.getArea()}`);
console.log(`長方形の周囲: ${calcPerim(5, 15)}`);
console.log(`円周率: ${PiValue}`);

この例では、calculatePerimeterPI に別名を付けてインポートしています。これにより、複雑なコードベースでの命名の衝突を避けつつ、コードを柔軟に管理できます。

名前付きエクスポートとデフォルトエクスポートの利点

名前付きエクスポートとデフォルトエクスポートを同時に使用することで、以下のような利点があります。

1. 明確な中心機能と補助機能の区別

デフォルトエクスポートをモジュールの中心機能に設定し、名前付きエクスポートで補助的な機能を提供することで、モジュールの設計が分かりやすくなります。

2. 柔軟なインポート

インポートする際に必要な機能だけを選んで使えるため、コードの無駄を省き、メモリ効率やパフォーマンスを向上させることができます。

3. エクスポート名の自由な変更

名前付きエクスポートをインポート時に別名を付けることができるため、プロジェクト内で名前の衝突を防ぐだけでなく、コードの可読性も向上します。

次に、モジュールのインポート方法をさらに詳しく見ていきましょう。

モジュールのインポート方法

TypeScriptやJavaScriptにおいて、モジュールを効率的に活用するためには、エクスポートされた要素を正しくインポートすることが重要です。インポート方法には、名前付きエクスポート、デフォルトエクスポート、そして両者を組み合わせたインポート方法があり、それぞれの用途に応じて適切な手法を選ぶ必要があります。

ここでは、モジュールの基本的なインポート方法と、それに関する応用的な使い方について解説します。

1. 名前付きエクスポートのインポート方法

名前付きエクスポートをインポートする際には、インポートしたい要素を波括弧 {} の中に指定します。これは、モジュールが複数の要素をエクスポートしている場合に、必要な要素だけを選択してインポートするのに適しています。

// mathUtils.ts
export const pi = 3.14159;
export function calculateCircumference(radius: number): number {
  return 2 * pi * radius;
}

// main.ts
import { pi, calculateCircumference } from './mathUtils';

console.log(`円周率: ${pi}`);
console.log(`半径5の円の円周: ${calculateCircumference(5)}`);

この例では、mathUtils.ts から picalculateCircumference を名前付きエクスポートとしてインポートしています。複数の要素がエクスポートされている場合でも、必要なものだけを選んでインポートできるのが特徴です。

2. デフォルトエクスポートのインポート方法

デフォルトエクスポートをインポートする場合、波括弧 {} を使用せずにインポートします。また、デフォルトエクスポートはインポート時に自由に名前を指定できるため、柔軟なコーディングが可能です。

// shapes.ts
export default class Rectangle {
  constructor(public width: number, public height: number) {}

  getArea(): number {
    return this.width * this.height;
  }
}

// main.ts
import Rectangle from './shapes';

const rect = new Rectangle(10, 20);
console.log(`長方形の面積: ${rect.getArea()}`);

この例では、shapes.ts から Rectangle クラスをデフォルトエクスポートとしてインポートしています。名前を自由に変更できるため、インポート先のコードに合わせた命名が可能です。

3. 名前付きエクスポートとデフォルトエクスポートの組み合わせインポート

名前付きエクスポートとデフォルトエクスポートを同時に使用しているモジュールの場合、両方を1つのインポート文で組み合わせてインポートすることができます。

// shapes.ts
export default class Rectangle {
  constructor(public width: number, public height: number) {}

  getArea(): number {
    return this.width * this.height;
  }
}

export function calculatePerimeter(width: number, height: number): number {
  return 2 * (width + height);
}

// main.ts
import Rectangle, { calculatePerimeter } from './shapes';

const rect = new Rectangle(10, 20);
console.log(`長方形の面積: ${rect.getArea()}`);
console.log(`長方形の周囲: ${calculatePerimeter(10, 20)}`);

この例では、Rectangle をデフォルトエクスポートとしてインポートし、calculatePerimeter を名前付きエクスポートとして同時にインポートしています。これにより、1つのインポート文で両方の機能を利用できるようになります。

4. 全ての名前付きエクスポートをまとめてインポート

場合によっては、モジュール内の全ての名前付きエクスポートをまとめてインポートしたいことがあります。その際には、import * as を使って、全てのエクスポートを1つのオブジェクトとして扱うことができます。

// mathUtils.ts
export const pi = 3.14159;
export function calculateCircumference(radius: number): number {
  return 2 * pi * radius;
}

// main.ts
import * as MathUtils from './mathUtils';

console.log(`円周率: ${MathUtils.pi}`);
console.log(`半径5の円の円周: ${MathUtils.calculateCircumference(5)}`);

この方法では、MathUtils オブジェクトに全てのエクスポートがまとめられ、インポートする際に1つのオブジェクトとして扱えるため、コードが整理されて扱いやすくなります。

5. インポート時のエイリアス使用

名前付きエクスポートをインポートする際に、エクスポート名とインポート名が衝突する場合や、より簡潔な名前を使いたい場合には、エイリアス(別名)を指定することができます。これは、特定のモジュールや機能に合わせた命名規則を柔軟に適用できるため、複雑なプロジェクトで非常に役立ちます。

// mathUtils.ts
export const pi = 3.14159;
export function calculateCircumference(radius: number): number {
  return 2 * pi * radius;
}

// main.ts
import { pi as PiValue, calculateCircumference as calcCirc } from './mathUtils';

console.log(`円周率: ${PiValue}`);
console.log(`半径5の円の円周: ${calcCirc(5)}`);

この例では、piPiValue に、calculateCircumferencecalcCirc という名前でインポートしています。エイリアスを使うことで、名前の衝突を避けつつ、コードを読みやすく整理できます。

次に、名前付きエクスポートとデフォルトエクスポートの応用例を紹介します。

名前付きエクスポートとデフォルトエクスポートの応用例

名前付きエクスポートとデフォルトエクスポートを使うことで、モジュールを柔軟かつ効率的に設計することができます。特に複数の機能やクラスを提供するモジュールにおいて、それぞれの役割を適切に分けることで、コードの可読性や保守性が向上します。ここでは、名前付きエクスポートとデフォルトエクスポートを活用したいくつかの応用例を紹介します。

1. 複数の関連機能を持つライブラリの設計

大規模なプロジェクトでは、複数の機能を持つライブラリを開発することがよくあります。このような場合、メインのクラスや機能をデフォルトエクスポートとして、補助的な機能を名前付きエクスポートで提供することで、使いやすいライブラリを構築することができます。

// geometry.ts
export default class Shape {
  constructor(public name: string) {}

  getDescription(): string {
    return `This is a ${this.name}`;
  }
}

export function calculateCircleArea(radius: number): number {
  return Math.PI * radius * radius;
}

export function calculateSquareArea(side: number): number {
  return side * side;
}

この例では、Shape クラスがデフォルトエクスポートされており、calculateCircleAreacalculateSquareArea が名前付きエクスポートとして提供されています。これにより、ライブラリのユーザーはメインの Shape クラスを中心に利用しつつ、必要に応じて個別の計算関数をインポートすることができます。

// main.ts
import Shape, { calculateCircleArea, calculateSquareArea } from './geometry';

const circle = new Shape('Circle');
console.log(circle.getDescription());
console.log(`半径10の円の面積: ${calculateCircleArea(10)}`);
console.log(`一辺5の正方形の面積: ${calculateSquareArea(5)}`);

このコードでは、Shape クラスをデフォルトエクスポートとしてインポートし、さらに必要な関数を名前付きエクスポートとしてインポートしています。このように、必要な要素だけを効率的にインポートできることが特徴です。

2. デフォルトエクスポートによるAPIの抽象化

デフォルトエクスポートは、モジュールが提供する主要な機能やAPIを抽象化するために使うことが多くあります。たとえば、以下のように、外部APIとのやり取りを簡潔に管理するためのモジュールをデザインできます。

// apiClient.ts
export default class APIClient {
  constructor(private baseUrl: string) {}

  async fetchData(endpoint: string): Promise<any> {
    const response = await fetch(`${this.baseUrl}/${endpoint}`);
    return response.json();
  }
}

export function formatData(data: any): string {
  return JSON.stringify(data, null, 2);
}

このモジュールでは、APIClient クラスがデフォルトエクスポートされています。このクラスはAPIとの通信を担当し、fetchData メソッドを使ってデータを取得できます。さらに、データフォーマットのための補助関数 formatData が名前付きエクスポートされています。

// main.ts
import APIClient, { formatData } from './apiClient';

const client = new APIClient('https://api.example.com');
client.fetchData('users')
  .then(data => {
    console.log('取得したデータ:', formatData(data));
  });

このように、APIクライアントをデフォルトエクスポートとして使うことで、主要な機能を簡単に利用でき、補助的な関数を名前付きエクスポートで提供することで、より柔軟な使用が可能になります。

3. ユーザーインターフェース(UI)コンポーネントのエクスポート

フロントエンド開発では、ReactやVue.jsなどのUIフレームワークを使用する際に、コンポーネントをエクスポートすることがよくあります。デフォルトエクスポートを使うことで、主要なUIコンポーネントを簡単にインポートでき、名前付きエクスポートで補助的なヘルパー関数やスタイルを提供することができます。

// button.tsx (React example)
import React from 'react';

export default function Button({ label }: { label: string }) {
  return <button>{label}</button>;
}

export function getButtonStyles(): string {
  return 'background-color: blue; color: white;';
}

この場合、Button コンポーネントがデフォルトエクスポートされており、getButtonStyles 関数が名前付きエクスポートされています。インポート時には、ボタンコンポーネントを中心に使いながら、スタイルヘルパーを必要に応じてインポートすることができます。

// app.tsx
import React from 'react';
import Button, { getButtonStyles } from './button';

function App() {
  const styles = getButtonStyles();

  return (
    <div>
      <h1>カスタムボタン</h1>
      <Button label="クリック" style={styles} />
    </div>
  );
}

export default App;

このように、デフォルトエクスポートを使用することで、主要なUIコンポーネントを簡単にインポートし、必要に応じて補助機能をインポートする柔軟なアーキテクチャを構築できます。

4. ライブラリの再利用性を高める

名前付きエクスポートとデフォルトエクスポートを適切に使うことで、モジュールの再利用性が向上します。デフォルトエクスポートで主要なクラスや機能を提供しつつ、名前付きエクスポートでサポート機能やヘルパーを提供することで、ユーザーが必要な部分だけを選んで使えるようになります。

次に、エクスポート・インポートのよくある間違いやトラブルシューティングについて解説します。

よくある間違いとトラブルシューティング

名前付きエクスポートとデフォルトエクスポートを使用する際には、いくつかのよくある間違いや注意点があります。これらの問題を理解し、適切に対処することで、エクスポート・インポートのトラブルを回避できます。ここでは、エクスポート・インポートに関連するよくある問題とその解決方法を解説します。

1. デフォルトエクスポートと名前付きエクスポートの混同

問題:
デフォルトエクスポートと名前付きエクスポートを混同して、import { default } from './module'; のように、デフォルトエクスポートを名前付きエクスポートの形式でインポートしようとしてしまう場合があります。

// module.ts
export default function greet() {
  console.log("Hello!");
}

// main.ts
// 誤り: デフォルトエクスポートを波括弧でインポートしようとしている
import { greet } from './module'; // エラー

解決方法:
デフォルトエクスポートは波括弧を使わずにインポートします。

// 正しいコード
import greet from './module';

greet(); // 正しく動作

2. 名前付きエクスポートの未インポート

問題:
名前付きエクスポートを使用しているが、必要な要素をインポートし忘れてエラーが発生する場合があります。

// mathUtils.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

// main.ts
import { add } from './mathUtils';

console.log(add(2, 3)); // 正しく動作
console.log(subtract(5, 2)); // エラー: subtractがインポートされていない

解決方法:
必要な名前付きエクスポートをすべてインポートするか、まとめてインポートする方法を選択します。

// 必要なエクスポートをすべてインポートする
import { add, subtract } from './mathUtils';

console.log(add(2, 3)); // 正しく動作
console.log(subtract(5, 2)); // 正しく動作

3. インポート名の衝突

問題:
異なるモジュールから同じ名前のエクスポートをインポートする際、名前の衝突が発生してエラーとなる場合があります。

// module1.ts
export const pi = 3.14159;

// module2.ts
export const pi = 3.14;

// main.ts
import { pi } from './module1';
import { pi } from './module2'; // エラー: 名前の衝突

解決方法:
エイリアスを使って、インポート時に別名を付けることで衝突を回避します。

import { pi as piModule1 } from './module1';
import { pi as piModule2 } from './module2';

console.log(`Module1のpi: ${piModule1}`);
console.log(`Module2のpi: ${piModule2}`);

4. エクスポート忘れ

問題:
関数や変数をモジュール内で定義したが、エクスポートし忘れた場合、インポートできずにエラーが発生します。

// module.ts
function greet() {
  console.log("Hello!");
}

// main.ts
import { greet } from './module'; // エラー: greetがエクスポートされていない

解決方法:
エクスポートを忘れずに明示的に行います。

// module.ts
export function greet() {
  console.log("Hello!");
}

5. サークル依存の発生

問題:
2つ以上のモジュールが相互に依存している場合、サークル依存が発生し、予期しない動作やエラーにつながることがあります。

// a.ts
import { bFunction } from './b';
export function aFunction() {
  bFunction();
}

// b.ts
import { aFunction } from './a';
export function bFunction() {
  aFunction();
}

解決方法:
依存関係を再設計し、モジュールが相互依存しないように構造を変更します。モジュール間の依存を整理するか、共通モジュールに依存関係をまとめることで解決できます。

// common.ts
export function commonFunction() {
  console.log("This is a common function");
}

// a.ts
import { commonFunction } from './common';

// b.ts
import { commonFunction } from './common';

これらのよくある間違いに対するトラブルシューティングを実施することで、エクスポート・インポート関連のエラーを減らし、コードの可読性と保守性を向上させることができます。

次に、本記事のまとめに移ります。

まとめ

本記事では、TypeScriptにおけるESModulesの名前付きエクスポートとデフォルトエクスポートについて詳しく解説しました。名前付きエクスポートは複数の要素を柔軟にインポートでき、デフォルトエクスポートはモジュールの主要な機能をシンプルに提供します。また、両者を混在させた使い方や、モジュールのインポート方法についても触れ、実際のコード例や応用例を紹介しました。最後に、よくある間違いとトラブルシューティングを解説することで、エクスポート・インポートの問題解決の参考にしていただけたかと思います。

適切にエクスポートとインポートを活用することで、コードの再利用性や保守性が大きく向上します。

コメント

コメントする

目次