こんにちは、かつコーチです。
前回は、「とりあえずany」から卒業するための考え方について解説しました。
TypeScriptで開発をしていると、自分のコードではなく外部のライブラリをimportしただけで型エラーが出て困る、という場面によく出会います。
その原因の多くが、今回扱う「型定義ファイル(@types)」の有無に関係しています。
外部ライブラリとTypeScriptの付き合い方を理解しておくと、原因不明に見えるエラーの正体がすっと見えるようになります。
なぜ外部ライブラリで型エラーが出るのか
JavaScriptで書かれたライブラリには型情報がない
世の中の多くのライブラリは、もともとJavaScriptで書かれています。
TypeScriptは型情報がないと、そのライブラリの関数やオブジェクトがどんな形なのか判断できません。
import { debounce } from "some-legacy-library";
debounce(() => console.log("実行"), 300);
// Could not find a declaration file for module 'some-legacy-library'.
このエラーは「ライブラリ自体が使えない」という意味ではなく、「TypeScriptにこのライブラリの型情報を教えてあげてください」という意味です。
私は最初このエラーを見たとき、ライブラリのインストールに失敗したのかと思い込み、何度も再インストールを繰り返していましたが、原因は型定義ファイルが不足していただけでした。
型定義ファイル(.d.ts)とは
型定義ファイル(.d.ts)とは、そのライブラリの関数や変数がどんな型を持っているかだけを記述した、実装を持たないファイルです。
// debounce.d.ts のイメージ
export function debounce(
func: (...args: unknown[]) => void,
wait: number
): (...args: unknown[]) => void;
実際の処理は書かれておらず、「この関数はこういう引数を受け取り、こういう値を返す」という設計図だけが書かれています。
TypeScriptはこの設計図を読み込むことで、JavaScriptで書かれたライブラリに対しても型チェックを効かせられるようになります。
@typesパッケージの探し方
DefinitelyTypedとは
型定義を持たないライブラリのために、有志が型定義だけをまとめて公開しているプロジェクトがDefinitelyTypedです。
DefinitelyTypedで公開されている型定義は、@types/ライブラリ名という名前のnpmパッケージとしてインストールできます。
npm install --save-dev @types/lodash
たとえばlodashというライブラリ自体には型定義が含まれていないため、@types/lodashを別途インストールすることで、TypeScript上でも安全にlodashを使えるようになります。
そのライブラリに@typesが必要かどうかの確認方法
すべてのライブラリに@typesパッケージが必要なわけではありません。
最近のライブラリは、最初から型定義を内蔵していることが多いからです。
確認手順は次の通りです。
- まずライブラリを
importしてみて、エラーが出るか確認する - エラーが出たら、
@types/ライブラリ名という名前のパッケージがnpmに存在するか検索する - 存在すれば
npm install --save-dev @types/ライブラリ名でインストールする - 存在しない場合は、後述する「自分で型定義を書く」方法を検討する
npmの検索画面や、https://www.npmjs.com/package/@types/ライブラリ名にアクセスして存在確認する方法が手軽でおすすめです。
package.jsonから型定義の有無を見分ける
ライブラリのpackage.jsonにtypesまたはtypingsというフィールドがあれば、そのライブラリ自体が型定義を内蔵しています。
{
"name": "some-modern-library",
"types": "dist/index.d.ts"
}
この場合は@typesパッケージを別途インストールする必要はなく、importした時点でそのまま型の恩恵を受けられます。
@typesを使うときのつまずきポイント
バージョンのズレによる型の不整合
私が実際につまずいた経験として、ライブラリ本体のバージョンと@typesパッケージのバージョンがズレていて、実際には存在するはずのメソッドが「存在しない」というエラーになったことがあります。
❌ Before:本体とtypesのバージョンを意識せずインストールする
npm install some-library@3.0.0
npm install --save-dev @types/some-library
# @types側が古いバージョン(例: 2.x系)向けのままインストールされてしまう
型定義だけが古いバージョンのままだと、本体には存在する新しいメソッドが「型の上では存在しない」ことになり、混乱の原因になります。
✅ After:本体のバージョンに合わせてtypesのバージョンも指定する
npm install some-library@3.0.0
npm install --save-dev @types/some-library@3.0.0
@typesパッケージにも、対象ライブラリのバージョンに対応したバージョンが存在することが多いため、なるべく本体と揃えてインストールするようにしましょう。
エラーの内容が「明らかに存在するはずのメソッドがない」という違和感のあるものだった場合は、まずバージョンのズレを疑うと解決が早くなります。
型定義自体が実態と合っていないケース
DefinitelyTypedは有志によるメンテナンスのため、まれに型定義が実際のライブラリの挙動と微妙にズレていることがあります。
その場合の対処法として、TypeScriptには型を上書き・拡張する仕組みが用意されています。
// 既存の型定義に、独自に補足したい型を追加する例
declare module "some-legacy-library" {
export function debounce(
func: (...args: unknown[]) => void,
wait: number,
immediate?: boolean // 型定義に含まれていなかった第3引数を追加
): (...args: unknown[]) => void;
}
declare moduleを使うことで、既存の型定義を上書きしたり、不足している部分を補ったりできます。
いきなりanyで逃げる前に、この方法で型を補完できないか検討する価値があります。
型定義が存在しないライブラリへの対応
自分で最小限の型定義ファイルを書く
@typesパッケージが存在せず、ライブラリ自体にも型定義がない場合は、自分で最小限の型定義ファイルを用意する方法があります。
// types/some-legacy-library.d.ts
declare module "some-legacy-library" {
export function debounce(
func: (...args: unknown[]) => void,
wait: number
): (...args: unknown[]) => void;
}
プロジェクト内にtypesフォルダなどを作り、そこに.d.tsファイルを配置し、tsconfig.jsonのincludeにそのフォルダを含めておくことで、TypeScriptがこの型定義を読み込んでくれるようになります。
すべての機能を細かく型付けする必要はなく、実際にプロジェクトで使うAPIだけに絞って書くのが現実的です。
応急処置としてのdeclareとanyの使い分け
どうしても時間がなく、型定義を書く余裕がない場合の応急処置として、declare moduleだけを書いて中身をanyにする方法もあります。
// types/urgent.d.ts
declare module "urgent-library";
このように書くと、そのモジュールの型はすべてanyとして扱われ、コンパイルエラーだけは回避できます。
ただし、これは前回解説したanyのデメリットをそのまま抱え込むことになるため、あくまで一時しのぎとして扱い、余裕ができたら型を補っていくことをおすすめします。
まとめ
この記事のポイント
- 外部ライブラリの型エラーの多くは、
.d.tsという型定義ファイルの不足が原因 @types/ライブラリ名をインストールすることで、型定義がないライブラリにも型チェックを効かせられる- ライブラリ本体と
@typesのバージョンはなるべく揃えてインストールする - 型定義が存在しない場合は、
declare moduleで自分なりの型定義を用意できる
次に読むべき記事
型定義の扱い方まで理解できれば、TypeScript基礎編もいよいよ最終回です。
次回は、既存のJavaScriptプロジェクトをTypeScriptへ移行するコツについて解説します。
→ 次の記事:JavaScriptプロジェクトをTypeScriptに移行するコツ