TypeScriptでimportとrequireの違いを理解しよう!モジュール管理の基本
生徒
「TypeScriptのコードを複数のファイルに分けたいのですが、読み込み方に『import』と『require』の2種類があることに気づきました。これらは何が違うんですか?」
先生
「それは非常に大切なポイントですね。一言で言えば、古い仕組みが『require』で、新しい標準の仕組みが『import』です。今のTypeScript開発では『import』が主流ですが、歴史を知るとより深く理解できますよ。」
生徒
「どちらを使えばいいか、詳しく教えてください!」
先生
「承知しました!それぞれの役割や書き方のルールを、初心者の方にもわかりやすく解説していきますね。」
1. モジュールとは?(プログラムの部品化)
プログラミングを始めたばかりの方にとって、「モジュール」という言葉は聞き慣れないかもしれません。簡単に言うと、モジュールとは「プログラムの部品」のことです。
一つの大きなファイルに何千行もコードを書くと、どこに何が書いてあるか分からなくなってしまいます。そこで、機能ごとにファイルを分割し、必要なときにそれを呼び出して使う仕組みが必要になります。この「分割されたファイル」をモジュールと呼び、それを呼び出す命令が import や require なのです。
例えるなら、料理のレシピ本です。「カレーの作り方」という一冊の本にすべてを書くのではなく、「野菜の切り方」「お米の炊き方」「煮込み方」と別々のカードに分けておき、必要なカードを机に並べて使うようなイメージです。
2. require(CommonJS)とは?
require は、もともと CommonJS(コモンジェイエス) という仕組みで使われていた書き方です。主にサーバー側で動くJavaScriptである「Node.js」で長年使われてきました。
パソコンを初めて触る方にとって「古い標準」と考えて差し支えありません。しかし、過去に作られた膨大なプログラム資産がこの形式で書かれているため、今でも目にすることが多いのです。TypeScriptでもこの形式を扱うことはできますが、新しくプログラムを書く際に自分で積極的に使う機会は減っています。
requireの書き方の例
例えば、別のファイルにある「算数(math)」という部品を読み込むときは、以下のように書きます。
// math.js などのファイルを読み込むイメージ
const math = require("./math");
const result = math.add(1, 2);
console.log(result);
3. import(ES Modules)とは?
import は、ES Modules(イーエスモジュール) と呼ばれる、現在のJavaScriptおよびTypeScriptの「世界標準」の書き方です。現在のモダンな開発(Webブラウザで動くアプリや、最新のフレームワーク)では、ほぼ100%こちらが使われます。
TypeScriptにおいても、この import を使うのが基本ルールです。import を使うことで、プログラムのどの部分が使われているかをコンピュータが事前に把握しやすくなり、動作が軽くなったり、間違いを見つけやすくなったりするというメリットがあります。
importの書き方の例
先ほどの算数ツールを import で読み込むと、次のようになります。
import { add } from "./math";
const result = add(1, 2);
console.log(result);
4. import と require の決定的な違い
この二つには、見た目以外にも大きな違いがいくつかあります。初心者の方が知っておくべきポイントを整理しましょう。
① 読み込まれるタイミングが違う
require は「動的」な読み込みと言われます。プログラムが動いている途中で「あ、これが必要だ」と思った瞬間に読み込みます。一方、import は「静的」な読み込みです。プログラムが動き出す前に、あらかじめ必要な部品をすべてチェックします。
「静的」 である import の方が、TypeScriptの得意分野である「事前のエラーチェック」と相性が非常に良いのです。
② TypeScriptでの推奨度
TypeScriptを使う最大の理由は、プログラムが動く前に「型(データの種類)」をチェックしてミスを防ぐことです。import を使うと、読み込んだ部品にどんなデータが入っているかを正確に追跡できるため、開発の効率が劇的に上がります。
③ 書き方のルールの違い
| 項目 | require (CommonJS) | import (ES Modules) |
|---|---|---|
| 標準 | 古い(Node.js向け) | 新しい(世界標準) |
| キーワード | const x = require("...") | import x from "..." |
| 拡張性 | 低い | 高い |
5. 具体的なコードで比較してみよう
実際に、ファイルを「書き出す(export)」側と「読み込む(import/require)」側のコードを比較してみましょう。TypeScriptでは、通常以下のような構成になります。
パターンA:最新の import / export(推奨)
まずは、おすすめの書き方です。ファイルを「部品」として公開するには export を使います。
// message.ts (部品を定義するファイル)
export const hello = "こんにちは!";
// main.ts (部品を使うファイル)
import { hello } from "./message";
console.log(hello);
実行結果は以下のようになります。
こんにちは!
パターンB:古い require / module.exports
次に、古い書き方です。ファイルを公開するために module.exports という呪文を使います。
// message.ts (古い書き方)
const hello = "こんにちは!";
module.exports = hello;
// main.ts (古い書き方)
const hello = require("./message");
console.log(hello);
初心者の方は、まずパターンAの import を完璧に覚えるようにしましょう。
6. TypeScriptの設定ファイル(tsconfig.json)との関係
少しだけ難しいお話をしますが、TypeScriptが import をどう扱うかは、設定ファイルで決まります。パソコンの中でプログラムを動かす準備をする際に、tsconfig.json という設定ファイルを確認することがあります。
ここで "module": "commonjs" と設定されていると、TypeScriptで書いた import は、コンピュータが理解しやすいように内部で自動的に require に変換されます。つまり、私たちは最新の import で書きつつ、コンピュータには古い require 形式で届ける、といった器用なことができるのです。これがTypeScriptの便利なところです。
7. 名前空間(namespace)について
モジュールの他に、「名前空間(namespace)」 という言葉を聞くこともあるかもしれません。これは、大規模な開発で名前がぶつからないように(例えば、二人以上のプログラマーが同じ「Message」という名前の部品を作ってしまった場合に混乱しないように)箱を作る仕組みです。
しかし、現代のTypeScript開発では、ファイルそのものが箱の役割を果たす モジュール(import/export) があれば、名前空間を使わなくても問題ないことがほとんどです。まずは import を使いこなすことに集中しましょう。
8. どちらを使うべきか迷ったら?
結論から言うと、「常に import を使う」 と考えて間違いありません。
なぜなら、現在のTypeScriptコミュニティや、React、Vue.js、Angularといった有名な開発ツールはすべて import を前提に作られているからです。もしインターネットで調べ物をしていて require という言葉が出てきたら、「あ、これは少し古い形式の解説だな」と判断できるようになれば、あなたはもう初心者脱出の第一歩を踏み出しています。
プログラミングは新しい技術が次々と出てきますが、この import は現在最も安定した、これから先も長く使われるルールです。しっかりと身につけていきましょう。