TypeScriptのimport typeは「型チェックのためだけに使うもの」を読み込む構文です。コンパイル後のJavaScriptから型専用のimport宣言は除去されます。一方、関数や変数など実行時に使う値は通常のimportが必要です。型と値の両方を同じモジュールから使う場合は、import { createUser, type User } のように書き分けます。
import typeと通常のimportの違い
| 項目 | import type | 通常のimport |
|---|---|---|
| 目的 | 型宣言のみを参照 | 実行時の関数・変数・クラスなどの値を参照 |
| JavaScript出力 | 宣言全体が消える | 設定に応じて値のimportが残る |
| 実行時に使えるか | 使えない | 使える(対象の値が存在する場合) |
| 例 | interface・type aliasを型として利用 | 関数呼び出し・クラス生成など |
import typeの本質は、TypeScriptの型レベルとJavaScriptの実行時の値を分けることです。通常のimportでも設定次第で型だけのimportが省略されることはありますが、import typeなら型専用という意図を明示できます。
用語解説:Type-only import(型専用インポート)
コンパイル後のJavaScriptコードにそのimport宣言を残さないTypeScript構文。実行時の依存関係とは分けて考えます。
interfaceだけを読み込む具体例
Node.jsのES ModulesとNodeNextモジュール解決を利用する例です。TypeScriptのソースがmodels.tsであっても、相対importの指定は出力先に合わせて.js拡張子を記述しています。プロジェクトのmoduleResolutionにより解決ルールは異なります。
// models.ts
export interface User {
id: number;
name: string;
}
export function createUser(id: number, name: string): User {
return { id, name };
}
// app.ts
import type { User } from "./models.js";
function showName(user: User): string {
return user.name;
}
app.tsではUserは型位置でだけ使っています。このimport type宣言はJavaScript出力では削除されます。したがってUserというinterfaceをJavaScriptの値として参照することはできません。
値と型を両方読み込みたい場合
// app.ts
import { createUser, type User } from "./models.js";
const user: User = createUser(1, "佐藤");
console.log(user.name);
createUserは実行時に必要な関数、Userは型だけなので、それぞれ通常のimportとtype修飾子で区別します。コンパイル後のJavaScriptでは、Userの型指定が消え、createUserのimportと呼び出しが残ります。
// イメージ: app.js
import { createUser } from "./models.js";
const user = createUser(1, "佐藤");
console.log(user.name);
上記のJavaScriptは主要部分を示した説明用の出力例です。実際の出力はtsconfigやビルドツールの設定によって変わります。
import typeでクラスのnewや関数呼び出しはできない
classは型としても値としても利用できますが、import typeで取り込むと値としてのアクセスはできません。次の例ではnewを使う必要があるため通常のimportを使います。
// service.ts
export class UserService {
getName(): string {
return "Alice";
}
}
// app.ts
import { UserService } from "./service.js";
const service = new UserService();
console.log(service.getName());
import type { UserService } とした場合は、new UserService()のような値位置で利用できません。使いたいのが型情報なのか、実際に生成・呼び出す値なのかを判断します。
| 用途 | 選択 |
|---|---|
| UserService型で変数の型を定義 | import typeでよい |
| new UserService()で生成 | 通常のimportが必要 |
| createUser()を呼び出す | 通常のimportが必要 |
verbatimModuleSyntaxがある場合の重要な挙動
TypeScriptのverbatimModuleSyntaxを使うと、type付きの指定は出力から取り除かれ、typeがないimport/ exportは基本的にそのまま残るため、区別の重要性が高まります。
// 型だけなので宣言ごと消える
import type { User } from "./models.js";
// 同じ行で値と型を扱う
import { createUser, type User } from "./models.js";
注意点があります。verbatimModuleSyntax=trueでは、import { type User } のように波括弧内だけをtype修飾した場合、発行JavaScriptに空のimport {} from './models.js'が残ることがあります。型しか使わない場合は、宣言全体をimport typeにすると意図が明確です。
また、verbatimModuleSyntaxはCommonJSなどのモジュール設定との組み合わせでコンパイルエラーになる場合があります。tsconfigのmoduleとmoduleResolution、package.jsonのtypeを確認してください。
根拠:TypeScript公式「verbatimModuleSyntax」https://www.typescriptlang.org/tsconfig/verbatimModuleSyntax.html
よくあるエラーと確認順序
- 型としてしか存在しない宣言を実行時に呼ぼうとしていないか。interface・type aliasはJavaScriptの値にならない。
- クラス・関数をimport typeで受け取り、newや関数呼び出しで使おうとしていないか。
- NodeNextの相対importで拡張子を適切に指定しているか。
- verbatimModuleSyntaxとmoduleの設定が実際の出力形式に合っているか。
- 副作用のあるモジュールを取り込みたいなら、型だけのimportで実行を期待していないか。
型専用importはモジュールの実行を目的としません。初期化処理などの副作用が必要な場合は、目的に応じて通常のimportまたは副作用importを使用してください。
export typeやimport()の型参照はどう使うか
型を再公開するときはexport typeを使って、型専用であることを示せます。また、型位置だけでimport("…")を利用する記法もあります。
// index.ts
export type { User } from "./models.js";
// consumer.ts
type UserId = import("./models.js").User["id"];
型の再exportやimport()の型参照は、実行時に対応する関数を呼び出すものではありません。型と値の境界を保つための記法です。
関連するTypeScript記事
実際のコンパイル確認にはNode.js・VSCode環境構築の記事が参考になります。型定義を増やしすぎない整理にはkeyof typeofの解説も利用できます。
(TypeScriptの実行環境とコンパイルについては『TypeScriptの始め方|Node.jsとVSCodeで学ぶ開発環境構築ガイド【初心者向け完全解説】』をご参照ください)
(型情報の扱いと再利用については『TypeScript型定義自動化「keyof typeof」実践例と二重管理解消法』をご参照ください)
よくある疑問とまとめ
interfaceを普通のimportで読み込んではいけない?
必ずしもそうではありません。設定に応じて省略されます。ただし型専用であることを明示し、出力の挙動を把握するにはimport typeが役立ちます。
import typeならバンドル容量が必ず減りますか?
保証できません。ビルド・バンドル設定や他の参照に依存します。主な目的は型と実行時の値を区別することです。
クラスにもimport typeを使えますか?
型として参照する用途には利用できますが、そのimportを通じてnewするなど値位置には使えません。
import typeは型だけ、通常のimportは実行時の値を使うとき、と考えると判断しやすくなります。両方必要ならimport { 関数, type 型 } のように分離してください。
TypeScriptファイルのimportを一つ選び、その宣言がコンパイル後のJavaScriptに必要か確認してみてください。