TypeScriptの型定義は、自分のコードに const n: number と書くところまでは順調でも、外部ライブラリを入れた途端に赤い波線が消えず、手が止まりがちです。

自分で書いた型は通るのに、import したライブラリだけ「型がない」って怒られる。なんで?
.d.ts ファイルって中身を見てもよく分からない…あれ何を書いてるやつ?
window に自作のプロパティを足したいのに Property ... does not exist on type Window が出る。どう直せばいい?

この原因は、型注釈の書き方ではなく、実装を持たず型情報だけを宣言する仕組み、つまり型定義ファイル(.d.ts)とdeclareの領域にあります。この記事を読むと、既存の .d.ts を落ち着いて読めるようになります。型がないライブラリやグローバル変数に自分で型を足せるようになり、TS7016 のような型定義エラーを見た瞬間に次の一手が浮かぶようになります。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • 基本型は書けるが、外部ライブラリの型付けで詰まっている方
  • .d.ts ファイルを開いても何が書いてあるのか読み取れない方
  • declare の使いどころが曖昧で、いつ書けばいいか判断できない方
  • 型定義まわりのエラーメッセージを見て毎回検索している方

読み終えるころには、型定義を「なんとなく怖い領域」ではなく、「エラー文言を頼りに直せる作業」として捉えられるようになります。

それでは、順を追って詳しく見ていきましょう!

型定義とは何か|型注釈との違いをまず切り分ける

TypeScriptで「型定義」と言うとき、それは const n: number のような型注釈とは別物で、実装を持たず型情報だけを宣言するものを指します。型注釈は値や関数に「これは number だ」と印を付ける行為で、実装コードと同じ場所に書きます。一方で型定義は、実装がどこか別にある前提で「この名前はこういう型を持つ」とだけ約束します。

// 型注釈:実装と型が同じ場所にある
const n: number = 3;
function greet(name: string): string {
  return `Hello, ${name}`;
}

// 型定義:実装を持たず、型だけを宣言する(.d.ts の中身のイメージ)
declare function greet(name: string): string;

例の declare function には中身の { ... } がありません。実装は別の場所(多くはJavaScript)にあり、TypeScriptには型だけを教えるという形です。この「型だけを宣言する」書き方が、型定義ファイル(.d.ts)や declare の正体です。

なかむぅ
なかむぅ
typeとinterfaceの違いと使い分けで迷ったら、こちらで整理しています。
TypeScriptのinterfaceとtypeの違いって?3つの違いと使い分け方TypeScriptで型を書くとき、type と interface のどちらでも同じように書けてしまい、「結局どっちを使えばいいの?」...

なぜ.d.tsやdeclareが必要になるのか

.ts ファイルに型を書けるのに別の仕組みが要るのは、型を付けたい対象の実装がJavaScript側にしかない場面があるからです。TypeScriptはコンパイル時に型を消してJavaScriptを出力するため、素のJavaScriptには型情報が残りません。そのJavaScriptに後から型を教える役目を、.d.tsdeclare が担います。

型情報が欠けていて困るのは、たとえば次のような場面です。

  • JavaScriptで書かれたライブラリを import して使うとき
  • window やグローバルに独自プロパティが差し込まれているとき
  • ビルドで生成されたJavaScriptなど、TypeScriptの型が付いていない成果物を扱うとき

実際に型定義のないライブラリをそのまま import すると、コンパイル時に型が見つからないと知らされます。

import _ from "lodash"; // 例:TS7016 Could not find a declaration file for module 'lodash'.(TypeScriptバージョンにより文言が異なる場合あり)

_.chunk([1, 2, 3, 4], 2);

このエラーは「lodash の実装(JavaScript)はあるが、型の宣言ファイルが見つからない」という意味です。実装があっても型定義がなければTypeScriptは型を付けられない——これが型定義という仕組みを必要とする根本の理由です。

型定義ファイル(.d.ts)の正体|中身の読み方

.d.ts ファイルには、実装を含まない型の宣言だけが書かれています。拡張子の d は declaration(宣言)を表し、関数や変数の「形」だけを列挙したカタログのようなものだと考えると読みやすくなります。

// math.d.ts
export declare function add(a: number, b: number): number;
export declare const version: string;

この add には関数の中身がありません。(a: number, b: number) を受けて number を返す、というシグネチャ(型の形)だけを宣言しています。.d.ts を読むときは「ここに実装はない。対応する実装は別のJavaScriptにある」と前提を置いて、名前と型の対応表として目を通すのがコツです。

こうした .d.ts は手で書くだけでなく、自動でも生成されます。tsconfig.jsondeclaration を有効にすると、.ts の実装から型情報だけを抜き出した .d.ts が出力されます。

{
  "compilerOptions": {
    "declaration": true
  }
}

この設定でビルドすると、出力先を指定していなければ math.ts と同じ場所に math.d.ts が生成されます(outDirdeclarationDir を指定していれば、その出力先に置かれます)。これで自作ライブラリを配布する際に「実装(JS)+型(.d.ts)」の形で型を同梱できます。自分が普段書く .ts の型が、そのまま .d.ts の元になっているとわかると、.d.ts の中身がぐっと身近に見えてきます。.d.ts の構造は、公式ドキュメントの TypeScript Handbook「Declaration Files > Deep Dive」 で詳しく確認できます。

型定義が「ある/別途入れる/ない」の3パターンを見分ける

ライブラリの型定義は、供給のされ方が3通りに分かれます。まずどのパターンかを見分ければ、次にやるべきことが見えやすくなります

  1. パッケージ同梱:ライブラリ自身が .d.ts を持っている
  2. @types で別途インストール:型定義が別パッケージで配布されている
  3. どこにもない:型定義が存在せず、自分で足すしかない

見分け方と対処を一覧にすると次のとおりです。

供給パターン 確認方法 対処
パッケージ同梱 package.jsontypes(または typings)フィールドや、同梱の .d.ts の有無 そのまま import すれば型が効く
@types で別途インストール @types/ライブラリ名 がnpmに公開されているか npm i -D @types/ライブラリ名 を追加する
どこにもない 上記いずれも見つからない 自分で最小の .d.ts を書いて型を宣言する

たとえば lodash は本体に型を同梱していないため、型定義パッケージを追加すると解決します。

npm i -D @types/lodash

これで import _ from "lodash"; に型が付き、_.chunk などを補完や型チェックの効いた状態で使えるようになります。パッケージの types フィールドを見る なければ @types を探す それもなければ自分で書く、という順で確認すれば、型がない状況でも迷わず動けます。

declare(アンビエント宣言)の役割と書き方

declare は、「実装はよそにある。型だけをここで約束する」という宣言を作るキーワードです。TypeScriptはこの宣言を「アンビエント宣言」と呼びます。アンビエント(ambient)は「周囲に既に存在している」というニュアンスで、実体はすでに実行環境にある前提で、その型だけを教える役割だと捉えると腑に落ちます。

declare const APP_VERSION: string;
declare function track(event: string): void;
declare class Widget {
  render(): void;
}

いずれも中身を持たず、名前と型の対応だけを宣言しています。declare を付けた宣言に実装を書こうとするとエラーになります。アンビエントな文脈では実装を持てないためです。

declare function track(event: string): void {
  // 例:TS1183 An implementation cannot be declared in ambient contexts.(TypeScriptバージョンにより文言が異なる場合あり)
  console.log(event);
}

declare は「型だけを約束する」ものなので、中身の { ... } は書けないと覚えておくと、この種のエラーで戸惑いません。宣言の基本的な考え方は TypeScript Handbook「Declaration Files > Introduction」 が出発点になります。

グローバル変数・window拡張に型を付ける

独自のグローバル変数をコードから使おうとすると、TypeScriptはその名前を知らないため名前解決に失敗します。

myGlobal.doSomething(); // 例:TS2304 Cannot find name 'myGlobal'.(TypeScriptバージョンにより文言が異なる場合あり)

これを解消するには、そのグローバルの型を declare で宣言します。スクリプト(importexport を持たないファイル)の直下では、declare constdeclare function をそのまま書けます。

// スクリプトファイルの直下
declare const myGlobal: {
  doSomething(): void;
};

myGlobal.doSomething(); // OK:型が付いた

ここで注意したいのが、ファイルに importexport が一つでもあると、そのファイルはモジュール扱いになり、直下の declare はグローバルではなくそのモジュール内のスコープになる点です。モジュールの中からグローバルに型を足したいときは、declare global ブロックで囲みます。

export {}; // このファイルはモジュール

declare global {
  interface Window {
    myApp: {
      version: string;
    };
  }
}

window.myApp.version; // OK:Window に型が足せた

モジュール内では素の declare がグローバルに届かないため、declare global が必要になる——この条件を押さえておくと、「スクリプトでは通ったのにモジュールでは通らない」という混乱を避けられます。

外部JSライブラリに自分で型を付ける(declare module)

@types も同梱型定義もないライブラリは、declare module で自分の手元に最小の型定義を用意できます。やり方は「まったく型がないモジュールを新しく宣言する」場合と、「既存モジュールの型に足す」場合の2つに分かれ、この2つは用語も役割も別です。

型がないモジュールには、import 文で書くモジュール名("some-lib" のようなパス)を、そのまま declare module "..." の名前に指定して宣言します。この名前が import ... from "some-lib" の指定子と一致することで、「そのパスを読み込んだときの型」としてTypeScriptに認識される、という仕組みです。

// types/some-lib.d.ts
declare module "some-lib" {
  export function doThing(x: number): string;
  export const VERSION: string;
}

これで import { doThing } from "some-lib"; が型付きで通るようになります。中身は最小限でよく、まず自分が使う関数だけ宣言して手を進め、必要になったら足すのが現実的です。

一方、既に型を持つモジュールへ後から型を追加したいときは、既存の型宣言に自分の宣言を合流させます。これはモジュール拡張(module augmentation)と呼ばれる使い方です。

// 既存ライブラリの Request 型に、独自プロパティを足すイメージ
export {};

declare module "express-serve-static-core" {
  interface Request {
    userId?: string;
  }
}

同じ declare module でも、「型がないモジュールを新規に宣言する」のか「既存の型へ合流させる」のかで意味が変わります。モジュール拡張が期待どおり効くかどうかは、そのファイルがモジュールかスクリプトか(importexport の有無)にも左右されるため、import/export の基礎を押さえておくと挙動を追いやすくなります。アンビエントモジュールの詳しい仕様は、公式ドキュメントの TypeScript Handbook「Modules > Ambient Modules」 で確認できます。

型定義に登場する4つのキーワードを整理する

.d.ts を読み進めると、declare 以外にもいくつかの語が繰り返し出てきます。それぞれの役割を先に整理しておくと、初見の型定義ファイルでも迷いにくくなります。よく出る4語を一覧にします。

キーワード 役割 いつ使う
declare 実装を持たず型だけを宣言する(アンビエント宣言) グローバル変数・関数・外部JSに型を付けるとき
.d.ts 型宣言だけを収めるファイル 型定義を実装から切り離して置く/配布するとき
@types DefinitelyTyped で配布される型定義パッケージ群 本体に型がないライブラリの型を別途入れるとき
triple-slash(/// <reference> 別の型定義ファイルへの参照を指示するディレクティブ 限定用途(下記参照)

@types は、DefinitelyTyped というコミュニティ管理のリポジトリで集約・配布されている型定義の総称です。npm i -D @types/xxx で入るものは、ここから公開されています。

triple-slashディレクティブは、宣言ファイルで別の型定義や組み込みlibへの依存を明示するときに使われます。通常の .ts@types を選ぶ用途なら、「tsconfigtypes や通常の import で足りることが多い」くらいに押さえると十分です。既存の .d.ts で見かけたときに「参照を指示している行だ」と読めれば十分で、新しく書くコードで積極的に使う場面は多くありません。

ここまでの4語のほかに、古い型定義やグローバルAPIの宣言では namespace という語を見かけることもあります。これはモジュールとは別の、名前空間を表す仕組みです。

型定義でよく出るエラーと対処(逆引き)

型定義まわりのエラーは、文言から使うべきキーワードの見当を付けやすいため、逆引きで押さえておくと復旧が速くなります。代表的なものを表にします。

エラーコード / 文言 主な原因 対処
TS7016 Could not find a declaration file for module ‘xxx’. ライブラリに型定義がない @types/xxx を入れる。なければ declare module "xxx" で最小の .d.ts を書く
TS2304 Cannot find name ‘xxx’. グローバル名の型が未宣言 declare constdeclare function、モジュール内なら declare global で宣言する
モジュール拡張が効かない ファイルがスクリプト扱い、または宣言先モジュールの指定違い importexport の有無を見直し、declare module "既存名" の対象を合わせる

(各エラー文言は tsc で確認できるものを基準にしています。TypeScriptバージョンにより文言が異なる場合があります。)

エラーを見たとき、頭の中で次の順にたどると迷いません。

  1. 型がないと言われたら、まず供給パターンを確認する(同梱 / @types / なし)
  2. @types があれば入れて終わり
  3. なければ自分で最小の .d.ts を書くdeclare moduledeclare global

このフローを持っておけば、「エラー文言 供給パターンの確認 適切なキーワードの選択」という順で考えられます。型定義まわりで詰まっても、毎回検索頼みにならずに手を動かしやすくなります。

【付録】さらに学びを深めるためのリソース


さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。

おすすめの書籍

ゼロからわかる TypeScript入門


技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。

変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。

プロを目指す人のためのTypeScript入門


技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。

オンラインで参照できる公式ドキュメント

TypeScript公式ハンドブック


https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。

TypeScript Deep Dive


https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。

TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。


まとめ – 型注釈と型定義の役割分担で.d.tsとdeclareに詰まらない

この記事では、型定義ファイル(.d.ts)とdeclareの役割・書き方・読み方を整理しました。

  • 型注釈(.ts) は実装と同じ場所に型を書くもの、型定義(.d.ts/declare) は実装を持たず型だけを宣言するもの、と役割が分かれる
  • ライブラリの型は「同梱 / @types で別途インストール / なし」の3パターンで供給され、まずどれかを見分けると対処しやすくなる
  • declare はアンビエント宣言で実装を書けない。グローバルは declare constdeclare global、外部モジュールは declare module で型を足す
  • TS7016 は型定義の不足、TS2304 は名前の未宣言。文言から使うキーワードの見当を付けられる

基本の指針はシンプルです。新規のコードは .ts に普通に型を書き、外部JSやグローバル拡張のように実装がJavaScript側にしかないときだけ .d.tsdeclare を使う——こう分けておけば、型定義は「怖い領域」ではなく、必要な場所で必要なだけ使う道具になります。

※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。