モジュールでコードを整理する
モジュールでコードを整理する
本記事について
要約
ここでは、JavaScriptの開発でモジュールがどのように使われ、どのようにコードを整理するのかを学べます。モジュールは、コードを論理的な塊に分割し、読みやすく、保守しやすいコードを作成するのに役立ちます。この学習ユニットでは、モジュールのサポートが年月とともにどのように進化してきたか、モジュールを定義する基本的な構文、異なるインポートスタイルの識別、名前付きエクスポートが読み取り専用プロパティになる方法などを学ぶことができます。
前提
Trailheadの英語版をChatGPT4(以下、ChatGPT)に投げて返ってきた内容を載せています。もともとはTrailheadのみで学習していたのですが、不自然な日本語や、JavaScript初学者に理解しづらい用語・概念を手っ取り早く調べるとためにChatGPTとやり取りしながらの学習に切り替えました。
ChatGPTに聞くとおおよそ同じような答えが返ってきますが、毎回聞くのも手間ですし、用語なども追加で聞いていることもあるので備忘録を兼ねて残します。
僕と同じようなJavaScript初学者の方に参考になれば幸いです。
対象のTrailhead↓
Organize Code with Modules 単元 | Salesforce Trailhead
学習目標
このユニットを完了すると、以下ができるようになります:
- モジュールのサポートがどのように進化してきたかを説明する。
- モジュールを定義するための基本的な構文を理解する。
- 異なるインポートスタイルを見分ける。
- 名前付きエクスポートが読み取り専用のプロパティにどのように結果するかを示す。
より良いモジュールの必要性
他の言語から移行してきた開発者なら、モジュラー(部分ごとの)プログラミングの重要性を理解しているでしょう。モジュラープログラミングとは、コードを論理的なチャンクに分割して、アクセスしやすくすることです。モジュールを使用すると、一般的にコードが読みやすく、保守しやすくなります。
しかし、ES6が登場するまで、JavaScriptでモジュールを作るのは簡単ではありませんでした。閉じ込められた関数とクロージャを作るか、Asynchronous Module Definition(AMD)やUniversal Module Definition(UMD)、あるいはNode.js開発を行っていたならCommonJSのような競争するモジュール仕様のいずれかに頼る必要がありました。
ES6では、待望のネイティブモジュールシステムが導入されました。しかし、これは他のES6の機能からは独立しており、長い間、主要なブラウザはそれをサポートしていませんでした。しかし、ついに変化が訪れ、ほとんどのブラウザがHTML5のスクリプトタグに type="module"
属性をつけることでES6モジュールをロードできるようになりました。
モジュールの基本
モジュールは作成して使用するのが非常に簡単です。ES6モジュールは基本的にはJavaScriptの一部を含むファイルです。モジュール内のすべてのものはそのモジュールだけにスコープされています。何か(関数、変数、クラスなど)を他の場所で利用可能にするには、export
ステートメントを使用する必要があります。その後、import
ステートメントを使用して、エクスポートされたモジュールから何を使用するかを指定します。
- Google Chrome
ブラウザで、https://playcode.ioに移動します。
2. Open Editorをクリックします。
3. テンプレートからJavaScriptを選択します。
4. script.jsの全てのコードを以下のコードに置き換えます:
import { printMsg } from './module1.js';
import { msg2, msg1 } from './module2.js';
printMsg(msg1 + msg2);
- Filesパネルから、srcフォルダを右クリックしてNew > JavaScriptを選択し、srcフォルダに新しいJavaScriptファイルを追加します。
- 名前を求められたら、バックスペースを押して
module1.js
と入力します。 - 次のコードを入力します:
export function printMsg(message) {
const div = document.createElement('div');
div.textContent = message;
document.body.appendChild(div);
}
- srcフォルダに
module2.js
という名前の新しいファイルを作成します。 - 次のコードを入力します:
let msg1 = 'Hello World! ';
let msg2 = 'This message was loaded from a module.';
export { msg1, msg2 };
- index.htmlファイルを選択します。
- ファイルを下にスクロールし、
script.js
ファイルをロードするscriptタグを探します。 - そのコード行を次の行に置き換えます。
<script type="module" src="./src/script.js"> </script>
注目すべきは、scriptタグに type="module"
属性を追加しただけであることです。
- プレビューウィンドウには、"Hello World! This message was loaded from a module."というテキストが表示されるはずです。
モジュールの使用方法の違い
ここまで見てきたコードでは、エクスポートされた関数や変数は同じ名前でインポートされていました。それは問題ありません。しかし、関数や変数の名前を変更して、別の名前を使用したいときはどうすればいいでしょう?これはエイリアスを使用して行います。最後の例では、次のコードを使用して、module2ファイルから変数をインポートしました。
import { msg2, msg1 } from './module2.js';
printMsg(msg1 + msg2);
これら2つの変数の連結値は、同じ変数名(msg1 + msg2
)を使用して printMsg
関数にパラメータとして渡されました。しかし、これらの変数のうちの1つに別の変数名、たとえば msg3
を使用したいとしたら、次のようにコードを変更します:
import { msg2, msg1 as msg3 } from './module2.js';
printMsg(msg3 + msg2);
この時点で、もしmsg1変数名を使おうとすると、msg1が定義されていないという参照エラーが出ます。だから、エイリアスを設定したら、常にその名前を使用することを覚えておいてください。
別のシナリオを考えてみましょう。今度は、モジュールからすべてをインポートして、エクスポートの名前を指定することについて心配したくないとします。それも可能です。アスタリスクを使用すると、すべてが一つのオブジェクトとしてインポートされます。script.jsのコードを次のように変更すると、これがどのように動作するかがわかります:
import { printMsg } from './module1.js';
import * as message from './module2.js';
printMsg(message.msg1 + message.msg2);
新しく作成されたオブジェクトには as
キーワードを使用してエイリアスが割り当てられていることに注意してください。また、オブジェクト名messageが変数の値を取得するときにどのように参照されているかにも注意してください。なので、インポートするときに名前を指定する必要はありませんが、それらをインポートされたオブジェクトのプロパティとして参照する前に、それらの名前を知る必要があります。
名前は全てを言う
モジュールのエクスポートを指すとき、それらを名前付きエクスポートと呼びます。しかし、実際にエクスポートされているのは何でしょうか?エクスポートされた変数、関数、クラスへの参照だけですか?それとも実際の変数、関数、クラスですか?
エクスポートされるのは名前だけで、これは自分で確認できます。変数をエクスポートし、その後インポートされたモジュールでその値を変更しようとするとエラーになります。つまり、それは読み取り専用です。例えば、script.jsのコードを次のように変更し、すべての変更をPlayCodeで保存すると、プレビューウィンドウは白紙になります。
import { printMsg } from './module1.js';
import { msg1, msg2 } from './module2.js';
msg1 = 'Did this variable change?';
printMsg(msg1 + msg2);
プレビューウィンドウが空白になるのは、TypeError: Assignment to constant variable.
というエラーメッセージが表示されているからです。これはブラウザのコンソールで確認できます。Chromeを開き、エラーメッセージを調査するために右クリックして Inspect
を選択し、Console
タブを選択します。
このエラーメッセージは、エクスポートされた名前が読み取り専用であることを示しています。だからこそ、それらの変数や関数を書き換えることはできません。
もっと詳しく教えて
- モジュールは常に厳密なモード(strict mode)で実行されます。これは、変数が必ず宣言されている必要があるということを意味します。
- モジュールは一度だけ実行されます。それはモジュールが読み込まれた直後になります。
- インポート文(import statements)は巻き上げられます。これは、全ての依存関係(dependencies)がモジュールが読み込まれた直後に実行されるということを意味します。