TypeScriptでimport型とdeclare moduleを使い分ける!初心者向け型定義ファイル完全攻略ガイド
生徒
「TypeScriptでライブラリを使おうとしたら、型が見つからないというエラーが出てしまいました。import型やdeclare moduleという言葉が出てきたのですが、どう使い分ければいいですか?」
先生
「それは型定義ファイルの設定に関する悩みですね。TypeScriptでは、自分で型を作る方法がいくつかありますが、特に外部のライブラリを扱うときには使い分けが重要になります。」
生徒
「なんだか難しそうですね。パソコン初心者でも理解できるでしょうか?」
先生
「大丈夫ですよ!お道具箱や名札に例えて、基本的なところから一つずつ丁寧に解説していきますね。」
1. 型定義ファイルとは何かを学ぼう
プログラミングの世界では、データに「これは数字ですよ」「これは文字ですよ」という名前を付けることを型(かた)と呼びます。TypeScriptはこの型をチェックしてくれる便利な言葉ですが、中には型の情報を持っていない古いライブラリ(便利な道具セット)もあります。
そこで登場するのが型定義ファイルです。これは、プログラム本体とは別に「この道具はこうやって使います」という説明書だけをまとめたファイルのことです。ファイル名の最後が .d.ts で終わるのが特徴です。このファイルがあるおかげで、TypeScriptは正しくプログラムをチェックできるようになります。
例えば、中身がわからない魔法の箱があったとします。その箱の外側に「中身はリンゴです」と書かれたシールを貼る作業が、型定義を作成することに似ています。このシールのおかげで、私たちは箱を開けなくても中身が食べ物だと判断できるのです。
2. declare moduleの役割と使い方
declare module(デクレア・モジュール)は、主に「既存のライブラリ全体に対して、新しく型を定義する」ときに使います。これは、世界中の誰かが作ったライブラリに対して、「この名前のライブラリは、こういう中身ですよ」とTypeScriptに教えてあげる役割を持っています。
例えば、あるおもちゃのセットに説明書が入っていなかったとします。あなたは自分専用のノートに「あのメーカーのおもちゃセットには、赤い車と青い人形が入っています」と書き込みます。これが declare module のイメージです。これを行うことで、プログラム全体でそのライブラリを安全に使えるようになります。
具体的な書き方を見てみましょう。以下のコードは、型定義が存在しない外部モジュールに型を付ける例です。
// custom-library.d.ts というファイルの中身
declare module "my-simple-library" {
export function sayHello(name: string): string;
export const version: number;
}
このように書くと、他のファイルで my-simple-library を読み込んだときに、sayHello 関数が使えることをTypeScriptが理解してくれます。
3. import型(import type)の役割とメリット
一方で、import型(インポート・タイプ)は、別のファイルで定義されている型情報を、今のファイルに持ってくるために使います。通常、プログラムを読み込むときは import を使いますが、これだとプログラムの動き(中身)まで読み込んでしまいます。
しかし、型チェックのためだけに情報が欲しい場合は、中身を読み込む必要はありません。そこで import type を使います。これは「型情報だけを貸してください」とお願いする方法です。これにより、最終的にプログラムが動くときのデータ量を軽くすることができるという大きなメリットがあります。
名札の例で考えてみましょう。あるイベントで、参加者の名前と年齢を確認したいとき、本人を連れてくるのが通常の import です。それに対して、本人は呼ばずに「名簿のコピーだけをもらう」のが import type です。チェックさえできればいいので、名簿だけで十分ですよね。
// types.ts というファイルで定義された型
export interface UserInfo {
id: number;
userName: string;
}
// main.ts で型だけを利用する
import type { UserInfo } from "./types";
const user: UserInfo = {
id: 1,
userName: "たろう"
};
4. どちらを使うべきか?使い分けの基準
初心者の方が一番迷うのが「結局どっちを書けばいいの?」という点です。使い分けの基準は非常にシンプルです。
declare module を使うべき場面は、「ライブラリそのものに型がないとき」や「既存のライブラリに自分独自の機能を追加したいとき」です。つまり、対象が外部のパッケージである場合に多く使われます。
import型 を使うべき場面は、「自分で作ったファイルの間で型を使い回したいとき」や「大きなライブラリから型定義だけを効率よく取り出したいとき」です。これは、自分のプロジェクト内の内部的な整理に向いています。
この違いを理解しておくと、エラーが出たときに「あ、これは外部のライブラリだから declare を使おう」とか「これは自分のファイルだから import type で十分だ」と判断できるようになります。
5. declare moduleでライブラリを拡張する
declare module のもう一つの強力な使い方は、拡張(かくちょう)です。すでに型定義があるライブラリに対して、「実はこういう機能も追加して使っているんだ」という情報を付け足すことができます。
例えば、ウェブサイトを作るための道具箱に、自分オリジナルの便利なシールを追加して貼っておくようなものです。他の人がその道具箱を開けたとき、あなたが貼ったシールのおかげで、使い方がすぐにわかるようになります。
以下のコードは、既存の express というライブラリの型を拡張する例です。ユーザー情報を保存する場所を追加しています。
// expressの型を拡張して、requestに独自のプロパティを追加する例
declare module "express-serve-static-core" {
interface Request {
userRole: string;
}
}
実行結果として、プログラム内で request.userRole と入力してもエラーにならず、正しく認識されるようになります。
// 型が拡張された後の動作イメージ
app.get("/", (req, res) => {
console.log(req.userRole); // エラーにならずに認識される!
});
6. import型の書き方のバリエーション
import type には、いくつかの書き方があります。ファイル全体から型だけを持ってくる方法と、通常の読み込みの中に混ぜて書く方法です。最近のTypeScriptでは、より細かく指定できるようになっています。
例えば、プログラムの動きを作るための関数と、型チェックのための情報を同時に読み込みたい場合、以下のように書くことができます。
// 関数と型を同時に読み込むが、型だけは明示的に指定する
import { someFunction, type SomeType } from "./my-module";
const data: SomeType = { name: "テスト" };
someFunction(data);
このように type というキーワードを中に入れることで、どれが型情報で、どれが実際のプログラムなのかが非常に分かりやすくなります。これは、初心者にとってもコードを読み解く大きなヒントになります。
7. よくあるエラーとその対処法
型定義を扱っていると、「Cannot find module」や「is not a module」といったエラーによく遭遇します。これらは、TypeScriptが型定義ファイルを見つけられていないことが原因であることがほとんどです。
対処法としては、まず tsconfig.json という設定ファイルを確認します。ここで include という項目に、自分が作った .d.ts ファイルが含まれているかチェックしましょう。ファイルが認識されていなければ、どんなに正しく declare module を書いても意味がありません。
また、ファイルの置き場所も重要です。一般的には types や @types といった名前のフォルダを作って、そこに型定義ファイルをまとめて管理するのが一般的です。整理整頓を心がけることで、エラーの少ない綺麗なプログラムを作ることができます。
8. 実践!自作ライブラリに型を付ける
最後に、より実践的な例を見てみましょう。あなたがもし、JavaScriptで書かれた古い計算ツールをTypeScriptで使いたいと思ったとき、どのように型を付けるべきでしょうか。
この場合、その計算ツールを一つのモジュールとして定義します。ここでは、足し算を行う簡単なツールに型を付けるコードを紹介します。
// calculator.d.ts
declare module "my-calculator" {
/**
* 二つの数字を足して結果を返します
*/
export function add(a: number, b: number): number;
/**
* 二つの数字を引いて結果を返します
*/
export function subtract(a: number, b: number): number;
}
このように定義しておけば、メインのプログラムでは以下のように安全に計算を行うことができます。もし数字以外のものを入れようとすれば、TypeScriptがすぐに教えてくれます。
import { add } from "my-calculator";
const result = add(10, 20);
console.log(result); // 30
9. 型定義ファイルを学ぶことで得られる未来
型定義ファイルや import型 の知識は、単なるエラー消しのための道具ではありません。これらを使いこなせるようになると、他の人が書いたコードを読み解く力が飛躍的に向上します。
また、大きなチームで開発をするときに、あなたが作った機能の「説明書(型定義)」がしっかりしていれば、他のメンバーは迷うことなくあなたのコードを使うことができます。これは、プログラミングにおけるコミュニケーション能力とも言えるでしょう。
パソコンを触ったことがない方にとって、最初は暗号のように見えるかもしれません。しかし、一つ一つの言葉の意味を理解し、実際にコードを書いて試してみることで、必ず自由に扱えるようになります。まずは import type から使い始めて、少しずつ declare module にも挑戦してみてください。