TypeScriptはJavaScriptに静的型付けを追加した言語で、現在のフロントエンド・バックエンド開発における標準ツールとなっています。本記事では、TypeScriptの基礎から実務で使える高度なテクニックまで体系的に解説します。
なぜTypeScriptを使うべきか?JavaScriptとの違い
TypeScriptを使うことで得られる主なメリットは以下の通りです。
- コンパイル時エラー検出:実行前にバグを発見できる
- エディタ補完の強化:VS Codeとの相性が抜群で開発効率が大幅アップ
- コードの自己文書化:型がドキュメントの役割を果たす
- リファクタリングの安全性:型チェックにより安全に変更できる
- 大規模開発での保守性向上:チーム開発で特に効果を発揮
TypeScriptの基本的な型
// プリミティブ型
const name: string = "Tech Athletes";
const age: number = 25;
const isActive: boolean = true;
const score: null = null;
const value: undefined = undefined;
// 配列
const numbers: number[] = [1, 2, 3, 4, 5];
const names: Array = ["Alice", "Bob", "Charlie"];
// タプル
const point: [number, number] = [10, 20];
const entry: [string, number] = ["key", 42];
// オブジェクト型
type User = {
id: number;
name: string;
email: string;
age?: number; // オプショナル
readonly createdAt: Date; // 読み取り専用
};
const user: User = {
id: 1,
name: "山田太郎",
email: "yamada@example.com",
createdAt: new Date()
};
// ユニオン型
type Status = "active" | "inactive" | "pending";
const status: Status = "active";
// 交差型
type WithTimestamp = { createdAt: Date; updatedAt: Date };
type UserWithTimestamp = User & WithTimestamp;
インターフェースとタイプエイリアスの使い分け
// インターフェース(extends で拡張可能)
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
// タイプエイリアス(ユニオン型、交差型に使いやすい)
type StringOrNumber = string | number;
type Nullable = T | null;
type ReadOnly = { readonly [K in keyof T]: T[K] };
// 実装例
class Labrador implements Dog {
name: string;
age: number;
breed: string;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.breed = "Labrador Retriever";
}
bark(): void {
console.log("Woof!");
}
}
ジェネリクス(Generics):TypeScriptの真髄
ジェネリクスはTypeScriptの最も強力な機能の一つで、型を引数として受け取ることができます。
// 基本的なジェネリクス
function identity(arg: T): T {
return arg;
}
const num = identity(42);
const str = identity("Hello"); // 型推論
// 制約付きジェネリクス
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // string型
const age = getProperty(user, "age"); // number型
// ジェネリクスを使ったAPIレスポンス型
type ApiResponse = {
data: T;
status: number;
message: string;
error?: string;
};
type UserListResponse = ApiResponse;
type SingleUserResponse = ApiResponse;
// Reactでのジェネリクス活用
function useState(initialValue: T): [T, (value: T) => void] {
let value = initialValue;
const setValue = (newValue: T) => { value = newValue; };
return [value, setValue];
}
const [count, setCount] = useState(0);
const [text, setText] = useState("");
実務で頻出するUtilityタイプ
type User = {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
};
// Partial: 全てのプロパティをオプショナルに
type PartialUser = Partial;
// { id?: number; name?: string; email?: string; ... }
// Required: 全てのプロパティを必須に
type RequiredUser = Required;
// Pick: 特定のプロパティだけ選択
type PublicUser = Pick;
// { id: number; name: string; email: string; }
// Omit: 特定のプロパティを除外
type UserWithoutPassword = Omit;
// Readonly: 全てを読み取り専用に
type ReadonlyUser = Readonly;
// Record: キーと値の型を指定したオブジェクト型
type UserRoles = Record;
const roles: UserRoles = { alice: "admin", bob: "user" };
// ReturnType: 関数の戻り値の型を取得
function createUser(name: string, email: string): User {
return { id: 1, name, email, password: "", role: "user" };
}
type CreatedUser = ReturnType; // User
// Parameters: 関数の引数の型を取得
type CreateUserParams = Parameters;
// [name: string, email: string]
// Awaited: Promiseの解決型を取得
async function fetchUser(): Promise {
return { id: 1, name: "Alice", email: "alice@example.com", password: "", role: "user" };
}
type FetchedUser = Awaited>; // User
TypeScript × React:実践的な型定義パターン
import { FC, ReactNode, useState, useCallback } from 'react';
// コンポーネントのProps型定義
type ButtonProps = {
children: ReactNode;
onClick: () => void;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
className?: string;
};
// FC(Function Component)型を使う
const Button: FC = ({
children,
onClick,
variant = "primary",
disabled = false,
className = ""
}) => {
return (
);
};
// カスタムフックの型定義
type UseFetchResult = {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
};
function useFetch(url: string): UseFetchResult {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
setLoading(true);
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const json: T = await response.json();
setData(json);
} catch (e) {
setError(e instanceof Error ? e : new Error("Unknown error"));
} finally {
setLoading(false);
}
}, [url]);
return { data, loading, error, refetch: fetchData };
}
tsconfig.jsonの最適設定
{
"compilerOptions": {
// 基本設定
"target": "ES2022", // 出力するJSのバージョン
"module": "ESNext", // モジュール形式
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// 厳格モード(推奨)
"strict": true, // 全ての厳格チェックを有効
"noUncheckedIndexedAccess": true, // 配列アクセスにundefinedを含める
"exactOptionalPropertyTypes": true,
// パス解決
"moduleResolution": "Bundler",
"paths": {
"@/*": ["./src/*"]
},
// 出力設定
"outDir": "./dist",
"rootDir": "./src",
"declaration": true, // .d.tsファイルを生成
"declarationMap": true,
"sourceMap": true,
// 品質チェック
"noUnusedLocals": true, // 未使用変数をエラー
"noUnusedParameters": true, // 未使用引数をエラー
"noImplicitReturns": true, // 全パスでreturnを要求
"noFallthroughCasesInSwitch": true,
// Next.js / React設定
"jsx": "preserve",
"allowJs": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true
},
"include": ["src/**/*", "next.config.ts"],
"exclude": ["node_modules", "dist"]
}
まとめ:TypeScriptでコード品質を劇的に向上させよう
TypeScriptは最初の学習コストこそかかりますが、プロジェクトが大きくなるほどその恩恵を感じられます。特にチーム開発では、型定義がコードの意図を明確に伝えるドキュメントとしても機能します。
本記事で紹介した型テクニックを活用することで、バグの早期発見、コードの可読性向上、リファクタリングの安全性確保が実現できます。まずは既存のJavaScriptプロジェクトにallowJs: trueで段階的に導入するのがおすすめです。