Loading

ROUTE

ルートゼロの
アクティビティ

TypeScriptのimport typeとimportの違い

1

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に必要か確認してみてください。

カジュアル面談はこちら

すべてを開示している会社です

ポジティブな面もネガティブな面も、包み隠さず誠実にお答えします。
気になることがあれば、面談の際にすべてオープンにお話します。