ウェブエンジニア問題集
第10章

モジュールと型のインポート/エクスポート

5
この章の目次開く

TypeScript/JavaScriptのモジュールシステムの基本と、TypeScript固有の型のインポート・エクスポートを扱います。

学習者学習者

import type って普通の import と何が違うの?わざわざ使い分ける意味あるの?

ESModulesの基本

名前付きエクスポートとデフォルトエクスポート

// 名前付きエクスポート(1ファイルに複数OK)
export function formatDate(date: Date): string {
  return date.toLocaleDateString('ja-JP');
}
 
export const MAX_RETRY = 3;
 
export type User = {
  id: string;
  name: string;
};
tsx
// デフォルトエクスポート(1ファイルに1つ)
export default function Button({ label }: { label: string }) {
  return <button>{label}</button>;
}
tsx

インポート

// 名前付きインポート
import { formatDate, MAX_RETRY } from './utils';
import { User } from './types';
 
// デフォルトインポート(好きな名前で受けられる)
import Button from './Button';
 
// 名前付き + デフォルトを同時に
import Button, { type ButtonProps } from './Button';
 
// 別名をつける
import { formatDate as formatJaDate } from './utils';
tsx
エクスポート方式メリットデメリット
名前付き自動補完が効く、rename可、tree-shaking対象なし
デフォルトimport時に好きな名前をつけられる自動補完が効きにくい、名前がバラつく
実務では名前付きエクスポートが主流。デフォルトエクスポートはReactコンポーネントのファイル(1ファイル1コンポーネント)でよく使われる。

import type — 型だけをインポートする

import type を使うと、コンパイル後のJavaScriptからインポート文が完全に除去されます。

// 値と型のインポートを分ける
import { fetchUser } from './api';
import type { User } from './types';
 
// inline type import(1行にまとめる書き方)
import { fetchUser, type User } from './api';
tsx

なぜ import type を使うのか

// ❌ import type を使わない場合
import { User } from './types';
// コンパイル後: import { User } from './types';
// → 実行時に ./types を読み込もうとする(中身は空なのに)
 
// ✅ import type を使う場合
import type { User } from './types';
// コンパイル後: (この行は消える)
// → バンドルサイズが小さくなる
tsx
先生先生

ESLintの @typescript-eslint/consistent-type-imports ルールを有効にすると、型のみのインポートを import type に自動修正してくれるよ。チームで統一するならこのルールがおすすめ。

型と値の名前空間

TypeScriptでは、型と値が別の名前空間に存在します。そのため、同じ名前で型と値を定義できます。

// class は型としても値としても使える
class User {
  constructor(public name: string) {}
}
 
const user: User = new User('Alice');  // User は型でもあり、コンストラクタ(値)でもある
tsx
// enum も型と値の両方の名前空間を持つ
enum Status {
  Active = 'active',
  Inactive = 'inactive',
}
 
const s: Status = Status.Active;  // 型としても値としても使える
tsx
// type は型の名前空間のみ
type Color = 'red' | 'blue';
// const c = Color;  // ❌ エラー: Color は型であり、値として使えない
tsx
キーワード型として値として
type
interface
class
enum
const / let
function

バレルファイル(index.ts)

関連する型やモジュールを1箇所から再エクスポートするパターンです。

// types/user.ts
export type User = { id: string; name: string; email: string };
export type CreateUserInput = { name: string; email: string };
 
// types/product.ts
export type Product = { id: string; title: string; price: number };
 
// types/index.ts(バレルファイル)
export type { User, CreateUserInput } from './user';
export type { Product } from './product';
tsx
// 使う側 — 1箇所からまとめてインポートできる
import type { User, Product } from '@/types';
tsx

パスエイリアス

tsconfig.jsonpaths 設定で、深いディレクトリ構造のインポートを短くできます。

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
json
// ❌ 相対パスが深い
import { Button } from '../../../components/ui/Button';
import type { User } from '../../../types/user';
 
// ✅ パスエイリアスですっきり
import { Button } from '@/components/ui/Button';
import type { User } from '@/types/user';
tsx

ちゃんと使うためのポイント

  • import type を使うと、コンパイル後のJSからインポート文が消え、バンドルサイズが最適化される
  • 名前付きエクスポートが主流。自動補完とtree-shakingに有利
  • 型と値は別の名前空間。classenum は両方に存在する
  • バレルファイルはインポートを楽にするが、tree-shaking への影響に注意
  • パスエイリアスで深い相対パスを避ける

次の章では、TypeScriptで既存のJavaScriptライブラリを型安全に使うための**型定義ファイル(.d.ts)**を扱います。

参考リンク

TypeScriptクイズに挑戦するこの章で学んだTypeScriptの知識を、4択クイズでアウトプットして定着させよう