LoginSignup
0
0

More than 1 year has passed since last update.

【初心者】エクスポート・インポート記述メモ( CommonJS Modules構文 )【備忘録】

Last updated at Posted at 2023-04-10

はじめに

投稿時点で、筆者は知識ゼロの状態から勉強を初めて2ヶ月程度の実力です。
そのため、理解不足や説明不足、誤った内容や呼び方をしている可能性があります。
万が一参考にする場合は上記の点を考慮した上でご一読ください。
説明文の内容はChatGPT先生からの引用も多いです。

本題

「CommonJS Modules」
以下、ChatGPT先生より引用。

ECMAScript ModulesとCommonJS Modulesは、JavaScriptのモジュールシステムの2つの主要な形式です。これらの形式は、異なるシステムで使用されるため、それぞれの違いを理解することが重要です。
CommonJS Modulesは、Node.jsで広く使用される形式です。この形式は、require関数を使用してモジュールを読み込み、module.exportsまたはexportsオブジェクトを使用してモジュールをエクスポートします。また、動的な構造を持ち、モジュールの読み込みは同期的に行われます。
簡単に言うと、CommonJS Modulesは、require関数を使用して、動的な構造を持ち、同期的にモジュールを読み込むことができます。

ECMAScriptのエクスポート・インポート記述については下記にまとめました。
【初心者】エクスポート・インポート記述メモ( ECMAScript Modules [ ESM ] 構文 )【備忘録】

ここでは関数、定数のエクスポート・インポートを例にしています。

エクスポート

エクスポート(出力)する手段は下記の3パターンがある。

  1. module.exports = 値
  2. module.exports.名前 = 値
  3. exports.名前 = 値

module.exports

エクスポート例(1つの値のみを渡す場合)

~ 通常の関数の場合 ~
1.名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
module.exports = function () { /*…*/ }; // 無名関数を直接エクスポート
const Counter = function () { /*…*/ }; // 無名関数をCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
2.名前付き関数(関数宣言 [ function文 ] )
module.exports = function Counter() { /*…*/ }; // Counterという関数を定義し、それを直接エクスポート
function Counter() { /*…*/ }; // 関数をCounterという関数名で定義
module.exports = Counter; // 関数Counterをエクスポート
function myFunction() { /*…*/ }; // 関数をmyFunctionという関数名で定義
const Counter = myFunction; // 関数myFunctionをCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
~ アロー関数の場合 ~

アロー関数では、通常の関数定義とは異なり、名前付き関数を定義することはできません。
アロー関数では function 文を使用せずに名前付き関数を定義することはできません。

名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
module.exports = () => { /*…*/ }; // 無名のアロー関数を直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート

エクスポート例(復数の値のみを渡す場合)

まずはNG例から紹介します。

~ NG例 ~

module.exports = function Counter() { /*…*/ }; // Counterという関数を定義し、それを直接エクスポート
module.exports = function Button() { /*…*/ }; // Buttonという関数を定義し、それを直接エクスポート
module.exports = () => { /*…*/ }; // 無名のアロー関数を直接エクスポート
module.exports = () => { /*…*/ }; // 無名のアロー関数を直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
module.exports = Button; // 定数Buttonをエクスポート

上記のパターンは全てNGです。
理由は、2回目のmodule.exportsが、1回目のmodule.exportsを上書きしてしまうためです。
複数の値を渡したい場合は下記のようなやり方があります。

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = {
  Counter: Counter, // 定数CounterをCounterという名前でエクスポート
  Button: Button // 定数ButtonをButtonという名前でエクスポート
};

これは2つとも同じ役割となり、どちらも分割代入を利用してエクスポートしています。
1つめの記述は2つめの記述を省略したやり方です。
2つめをみると、エクスポートする際にどれが何かを識別できるように名前を付与しているのがわかります。
このように、復数の値をエクスポートする際には名前を付与する必要があります。
もし別名でエクスポートしたい場合は下記の例がわかりやすいです。

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = {
  myCounter: Counter, // 定数CounterをmyCounterという名前でエクスポート
  myButton: Button // 定数ButtonをmyButtonという名前でエクスポート
};

実際は、複数のモジュールで同じ変数名を使用することが多いため、本来はプロパティ名と変数名を一致させるほうが一般的だそうです。(例:Counter: Counter
ここで、復数の値をエクスポートするやり方を紹介しましたが、復数の値を渡す際のやり方は他にもあります。
そのやり方は、module.exportsの後に.名前で名前を付与することです。
ですので、 module.exports.名前 = 値という記述になります。

module.exports.myCounter = function Counter() { /*…*/ }; // Counterという関数を定義し、それをmyCounterという名前で直接エクスポート
module.exports.myButton = function Button() { /*…*/ }; // Buttonという関数を定義し、それをmyButtonという名前で直接エクスポート
module.exports.myModule1 = () => { /*…*/ }; // 無名のアロー関数をmyModule1という名前で直接エクスポート
module.exports.myModule2 = () => { /*…*/ }; // 無名のアロー関数をmyModule2という名前で直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
module.exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート

ちなみに、下記2つは同じ役割になります。

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
module.exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = {
  myCounter: Counter, // 定数CounterをmyCounterという名前でエクスポート
  myButton: Button // 定数ButtonをmyButtonという名前でエクスポート
};

exports

module.exports.名前 = 値 の先頭のmojule.を消して、exports.名前 = 値 という記述でもエクスポートすることができます。
module.exportsexports は本質的に役割が異なるとChatGPT先生は仰っていますが、よくわかりませんでした。
自分は、exportsmodule.exportsの省略形、module.exportsexportsみたいに覚えています。
唯一異なるのは、module.exports = 値はOKだが、exports = 値はNGということぐらいでしょうか。
一応最後に、module.exportsexportsを比較した例を作ったので、よければご覧ください。
以下には、ChatGPT先生に質問した回答メモを残しておきます。

exportsを使用する意味は、module.exportsを簡略化することにあります。
つまり、exportsは、module.exportsオブジェクトへの参照を持つ特別なオブジェクトです。
これにより、exportsを使用して、module.exportsに直接プロパティを追加することができます。
しかし、exportsオブジェクトに直接プロパティを割り当てた場合、exportsオブジェクトがmodule.exportsオブジェクトと異なるオブジェクトになってしまい、予期しないエラーが発生する可能性があることに注意する必要があります。
そのため、一般的には、exportsを使用する代わりに、常にmodule.exportsを使用することをお勧めします。
ただし、複数の関数やオブジェクトをエクスポートする場合、exportsを使用してオブジェクトを構築することができます。

exportsオブジェクトは、module.exportsオブジェクトへの参照を持つ特別なオブジェクトであるため、exportsオブジェクトに直接プロパティを割り当てた場合、module.exportsオブジェクトとは別のオブジェクトになってしまいます。
この場合、exportsオブジェクトに割り当てたプロパティはmodule.exportsオブジェクトには影響を与えず、module.exportsオブジェクトは空のオブジェクトのままになります。

module.exportsは最初に空のオブジェクトとして作成されるため、exportsオブジェクトに直接プロパティを割り当てた場合、module.exportsオブジェクトには反映されません。
これは、exportsオブジェクトがmodule.exportsオブジェクトを参照しているものの、exportsオブジェクトに直接割り当てたプロパティはmodule.exportsオブジェクトには影響を与えないためです。
例えば、以下のようにmodule.exportsオブジェクトに直接プロパティを割り当てると、module.exportsオブジェクトにプロパティが追加されます。

module.exports.名前 = ;

しかし、exportsオブジェクトに直接プロパティを割り当てた場合は、module.exportsオブジェクトには反映されません。

exports.名前 = ;

この場合、exportsオブジェクトはmodule.exportsオブジェクトを参照していますが、module.exportsオブジェクト自体には何も割り当てられていないため、空のオブジェクトのままになります。
ですので、上記3つのパターンは下記のように置き換えることができます。

exports.myCounter = function Counter() { /*…*/ }; // Counterという関数を定義し、それをmyCounterという名前で直接エクスポート
exports.myButton = function Button() { /*…*/ }; // Buttonという関数を定義し、それをmyButtonという名前で直接エクスポート
exports.myModule1 = () => { /*…*/ }; // 無名のアロー関数をmyModule1という名前で直接エクスポート
exports.myModule2 = () => { /*…*/ }; // 無名のアロー関数をmyModule2という名前で直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート

ではここからは、最初のmodule.exportsの記述例を全てexportsに置き換えてみました。(自分がわかりやすくするため)

~ 通常の関数の場合 ~

1.名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
exports.Counter = function () { /*…*/ }; // 無名関数をCounterという名前で直接エクスポート
const myConst = function () { /*…*/ }; // 無名関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
2.名前付き関数(関数宣言 [ function文 ] )
exports.Counter = function myFunction() { /*…*/ }; // myFunctionという関数を定義し、それをCounterという名前で直接エクスポート
function myFunction() { /*…*/ }; // 関数をmyFunctionという関数名で定義
exports.Counter = myFunction; // 関数myFunctionをCounterという名前でエクスポート
function myFunction() { /*…*/ }; // 関数をmyFunctionという関数名で定義
const myConst = myFunction; // 関数myFunctionをmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート

~ アロー関数の場合 ~

名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
exports.Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという名前で直接エクスポート
const myConst = () => { /*…*/ }; // 無名のアロー関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート

まとめると、
復数の値を1つずつエクスポートする場合は

module.exports.Counter = function Counter() { /*…*/ }; // Counterという関数を定義し、それをCounterという名前で直接エクスポート
module.exports.Button = function Button() { /*…*/ }; // Buttonという関数を定義し、それをButtonという名前で直接エクスポート

最後にまとめてエクスポートする場合は、

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート

というやり方が良いのではないでしょうか。

インポート

インポート(読込)する手段は下記の2パターンがあります。

  1. const 名前 = require('./ファイル名');
  2. const { 名前 } = require('./ファイル名');

1.const 名前 = require('./ファイル名');の場合は、任意の名前を指名することができ、module.exports = 値としてエクスポートされた場合に使用できます。
2.const { 名前 } = require('./ファイル名');の場合は、名前をエクスポート時に指名した名前と一致させる必要があり、module.exports.名前 = 値、または、exports.名前 = 値としてエクスポートされた場合に使用できます。

下記に例をあげていきます。

1.エクスポートされた値をインポート

const Counter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、Counterという定数名で定義

2.復数エクスポートされた中から一部のみをインポートする場合

const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義

エクスポート時と同様で、分割代入を利用しています。
そして、上記は省略した記述方法となり、実際は下記のようになっています。

const {
  Counter : Counter, // Counterという名前のデータをCounterという名前でインポート
 } = require('./modules'); // './modules'からエクスポートされたデータをインポート

復数エクスポートする時と同じ記述と思えばわかりやすいかもしれません。

3.エクスポートされた値を1つにインポートする場合

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
const Counter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、Counterという定数名で定義

このとき、Counterには下記の2つの値が入っています。

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義

4.エクスポートされた中から一部のみをインポートする場合

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義

このとき、Counterには下記の値のみが入っています。

const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義

~ 複数の値をインポート ~

基本的にエクスポート時と同じ記述です。

const { Counter, Button } = require('./modules'); // './modules'からエクスポートされた、Counter・Buttonというそれぞれの名前のデータを、同じ名前でインポート

上記は省略した記述方法で、実際は下記のようになっています。

const {
  Counter : Counter, // Counterという名前のデータをCounterという名前でインポート
  Button : Button // Buttonという名前のデータをButtonという名前でインポート
 } = require('./modules'); // './modules'からエクスポートされたデータをインポート

もし名前を変更したい場合は下記のようになります。

const {
  Counter : myCountre, // Counterという名前のデータをmyCounterという名前でインポート
  Button : myButton // Buttonという名前のデータをmyButtonという名前でインポート
 } = require('./modules'); // './modules'からエクスポートされたデータをインポート

エクスポートとインポートを交えた例

1.シンプル(module.exports)

components/modules.js
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
components/index.js
const myCounter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myCounterという定数名で定義

2.復数エクスポート(module.exports)

components/modules.js
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
components/index.js
const myModules = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myModulesという定数名で定義

3.シンプル(exports)

components/modules.js
const myConst = () => { /*…*/ }; // 無名のアロー関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
components/index.js
const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義

4.復数エクスポート(exports)

components/modules.js
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
exports.Modules = { Counter, Button }; // 定数Counterと定数ButtonをModulesという名前でエクスポート
components/index.js
const { Modules } = require('./modules'); // './modules'からModulesという名前でエクスポートされた値をインポートして、Modulesという定数名で定義

5.復数エクスポート、復数インポート(exports)

components/modules.js
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート
components/index.js
const { myCounter, myButton } = require('./modules'); // './modules'からエクスポートされた、myCounter・myButtonというそれぞれの名前のデータを、同じ名前でインポート

NG例

components/modules.js
const myConst = () => { /*…*/ }; // 無名のアロー関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
components/index.js
const myCounter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myCounterという定数名で定義

exports.Counterという形式でエクスポートされた値をインポートする場合は、エクスポート時と同じ名前を指定して取り出す必要があります。
ですので正しく修正すると下記になります。

components/index.js
const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義

復数エクスポートの例で例えるとわかりやすいかもしれません。

NG例2

components/modules.js
const myConst1 = () => { /*…*/ }; // 無名のアロー関数をmyConst1という定数名で定義
const myConst2 = () => { /*…*/ }; // 無名のアロー関数をmyConst1という定数名で定義
exports.Counter1 = myConst1; // 定数myConst1をCounter1という名前でエクスポート
exports.Counter2 = myConst2; // 定数myConst2をCounter2という名前でエクスポート
components/index.js
const myCounter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myCounterという定数名で定義

復数にすると、なぜ指定して取り出す必要があるのかわかりやすくなったと思います。これでは、myCounterに何を当てればいいのか判断できませんね。
exportsでは、./modules内の値の全てではなく一部分にフォーカスを当てているというイメージです。

~ 応用 ~

module.exportsexportsは同時に使用可能です。

components/modules.js
module.exports.Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという名前で直接エクスポート
exports.Button = () => { /*…*/ }; // 無名のアロー関数をButtonという名前で直接エクスポート
components/index.js
const { Counter, Button } = require('./modules'); // './modules'からエクスポートされた、Counter・Buttonというそれぞれの名前のデータを、同じ名前でインポート

わかりやすくするために、console.logを使用した例を上げていきます。
例1

components/modules.js
module.exports.Counter = () => {
  return 'counter text';
};

exports.Button = () => {
  return 'button text';
};
components/index.js
const myModule = require('./modules');
console.log(myModule.Counter()); // 出力結果:counter text
console.log(myModule.Button()); // 出力結果:button text

もしインポートの仕方を変えた場合はこのような出力結果となります。

components/index.js
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:counter text
console.log(Button()); // 出力結果:エラー

例2

components/modules.js
module.exports = () => {
  return 'counter text';
};

exports.Button = () => {
  return 'button text';
};
components/index.js
const myModule = require('./modules');
console.log(myModule()); // 出力結果:counter text
console.log(myModule.Button()); // 出力結果:button text

間違えそうな例

components/modules.js
module.exports.Counter = () => {
  return 'counter text';
};

exports.Button = () => {
  return 'button text';
};
components/index.js
const Counter = require('./modules');
console.log(Counter()); // 出力結果:エラー
console.log(Button()); // 出力結果:エラー

// 正しくは下記

console.log(Counter.Counter()); // 出力結果:counter text
console.log(Counter.Button()); // 出力結果:button text

~ module.exports と exports の違いを知る ~

こうやったまとめると、全く違いがないようにみえます…。
実機で試さずに、ChatGPT先生に出力に聞きながら書いたので、間違っていたらすみません。

例1

components/modules.js
module.exports = () => {
  return 'counter text';
};
components/index.js
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:counter text

// パターン2
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:エラー

例2

components/modules.js
exports = () => {
  return 'counter text';
};
components/index.js
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:エラー

// パターン2
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:エラー

例3

components/modules.js
module.exports.Counter = () => {
  return 'counter text';
};
components/index.js
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:{ Counter: [Function: Counter] }

// パターン2
const Counter = require('./modules');
console.log(Counter.Counter()); // 出力結果:counter text

// パターン3
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:counter text

例4

components/modules.js
exports.Counter = () => {
  return 'counter text';
};
components/index.js
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:{ Counter: [Function: Counter] }

// パターン2
const Counter = require('./modules');
console.log(Counter.Counter()); // 出力結果:counter text

// パターン3
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:counter text

参考リンク

Node.js でのモジュールとエクスポートの方法
Node.js の exports と module.exports
モジュール化(importとrequireの違い)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0