TypeScript 特有のベストプラクティス
TypeScriptの型システム を最大限に活用するためのベストプラクティスを学びましょう。
学習のポイント
- any を避け、適切な型を使用する
- unknown と never を理解する
- ジェネリクスを活用する
- ユーティリティ型を使いこなす
- 型の絞り込みを行う
any を避ける
any は型チェックを無効にするため、TypeScriptの恩恵を受けられなくなります。
Bad
❌ Bad: any を使用
// ❌ any を使用
function processData(data: any): any {
return data.value.toUpperCase();
}
const result = processData({ value: 123 }); // ランタイムエラー
Good
✅ Good: 適切な型を定義
// ✅ 適切な型を定義
interface DataWithValue {
value: string;
}
function processData(data: DataWithValue): string {
return data.value.toUpperCase();
}
const result = processData({ value: 'hello' }); // 'HELLO'
// processData({ value: 123 }); // コンパイルエラー
unknown を使う
型が不明な場合は any ではなく unknown を使いましょう。
✅ Good: unknown で受け取り、型を確認してから使用
// ✅ unknown で受け取り、型を確認してから使用
function parseJson(json: string): unknown {
return JSON.parse(json);
}
const data = parseJson('{"name": "Taro"}');
// data.name; // エラー: 'unknown' 型にはプロパティがない
// 型ガードで絞り込む
function isUser(value: unknown): value is { name: string } {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof (value as { name: unknown }).name === 'string'
);
}
if (isUser(data)) {
console.log(data.name); // OK
}
never を理解する
never は絶対に発生しない値の型です。到達不能なコードや網羅性チェックに使用します。
// 網羅性チェック
type Color = 'red' | 'green' | 'blue';
function getColorCode(color: Color): string {
switch (color) {
case 'red':
return '#ff0000';
case 'green':
return '#00ff00';
case 'blue':
return '#0000ff';
default:
// 全てのケースを処理していれば、ここには到達しない
const exhaustiveCheck: never = color;
throw new Error(`Unknown color: ${exhaustiveCheck}`);
}
}
// 例外をスローする関数の戻り値
function throwError(message: string): never {
throw new Error(message);
}
ジェネリクスを活用する
ジェネリクスを使うことで、型安全で再利用可能なコードを書けます。
✅ Good: ジェネリクスで汎用的な関数
// ✅ ジェネリクスで汎用的な関数
function getFirst<T>(items: T[]): T | undefined {
return items[0];
}
const firstNumber = getFirst([1, 2, 3]); // number | undefined
const firstString = getFirst(['a', 'b']); // string | undefined
// ✅ 制約付きジェネリクス
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
interface User extends HasId {
name: string;
}
const users: User[] = [{ id: 1, name: 'Taro' }];
const user = findById(users, 1); // User | undefined
// ✅ 複数の型パラメータ
function map<T, U>(items: T[], fn: (item: T) => U): U[] {
return items.map(fn);
}
const lengths = map(['hello', 'world'], s => s.length); // number[]
ユーティリティ型を使いこなす
TypeScriptの組み込みユーティリティ型を活用しましょう。
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial: 全てのプロパティをオプショナルに
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }
// Required: 全てのプロパティを必須に
type RequiredUser = Required<PartialUser>;
// Pick: 特定のプロパティのみ抽出
type UserSummary = Pick<User, 'id' | 'name'>;
// { id: number; name: string }
// Omit: 特定のプロパティを除外
type UserWithoutEmail = Omit<User, 'email'>;
// { id: number; name: string; age: number }
// Record: キーと値の型を指定したオブジェクト型
type UserById = Record<number, User>;
// Readonly: 全 てのプロパティを読み取り専用に
type ReadonlyUser = Readonly<User>;
// ReturnType: 関数の戻り値の型を取得
function createUser(): User {
return { id: 1, name: 'Taro', email: 'taro@example.com', age: 25 };
}
type CreatedUser = ReturnType<typeof createUser>; // User
// Parameters: 関数のパラメータの型をタプルとして取得
type CreateUserParams = Parameters<typeof createUser>; // []
型の絞り込み(Type Narrowing)
条件分岐で型を絞り込むことで、より安全なコードを書けます。
// typeof による絞り込み
function process(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase(); // string として扱える
}
return value.toFixed(2); // number として扱える
}
// in 演算子による絞り込み
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
function makeSound(animal: Dog | Cat): void {
if ('bark' in animal) {
animal.bark(); // Dog として扱える
} else {
animal.meow(); // Cat として扱える
}
}
// instanceof による絞り込み
function formatError(error: unknown): string {
if (error instanceof Error) {
return error.message; // Error として扱える
}
return String(error);
}
// カスタム型ガード
interface ApiResponse<T> {
success: true;
data: T;
}
interface ApiError {
success: false;
error: string;
}
type ApiResult<T> = ApiResponse<T> | ApiError;
function isSuccess<T>(result: ApiResult<T>): result is ApiResponse<T> {
return result.success === true;
}
function handleResult<T>(result: ApiResult<T>): T | null {
if (isSuccess(result)) {
return result.data; // ApiResponse<T> として扱える
}
console.error(result.error); // ApiError として扱える
return null;
}
const assertion
as const を使うことで、リテラル型として推論させることができます。
// as const なし
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
};
// 型: { apiUrl: string; timeout: number }
// as const あり
const configConst = {
apiUrl: 'https://api.example.com',
timeout: 5000,
} as const;
// 型: { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }
// 配列での使用
const colors = ['red', 'green', 'blue'] as const;
// 型: readonly ["red", "green", "blue"]
type Color = typeof colors[number]; // "red" | "green" | "blue"
テンプレートリテラル型
文字列リテラル型を組み合わせて新しい型を作成できます。
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = '/users' | '/posts' | '/comments';
// テンプレートリテラル型
type ApiRoute = `${HttpMethod} ${Endpoint}`;
// "GET /users" | "GET /posts" | "GET /comments" | "POST /users" | ...
// イベント名の型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // "onClick"
type ChangeEvent = EventName<'change'>; // "onChange"
練習問題
以下のコードをTypeScriptの型機能を活用してリファクタリングしてください
// 問題
function fetchData(url: any): any {
// APIからデータを取得
return fetch(url).then(res => res.json());
}
function processItems(items: any[]): any[] {
return items.map(item => ({
id: item.id,
name: item.name.toUpperCase(),
}));
}
解答:
✅ Good: リファクタリング後
// ✅ リファクタリング後
interface FetchOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
body?: string;
}
async function fetchData<T>(url: string, options?: FetchOptions): Promise<T> {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
interface RawItem {
id: number;
name: string;
}
interface ProcessedItem {
id: number;
name: string;
}
function processItems(items: RawItem[]): ProcessedItem[] {
return items.map(item => ({
id: item.id,
name: item.name.toUpperCase(),
}));
}
// 使用 例
interface User {
id: number;
name: string;
email: string;
}
async function getUsers(): Promise<User[]> {
return fetchData<User[]>('/api/users');
}
改善ポイント:
- any を排除し、具体的な型を定義
- ジェネリクスで再利用可能な fetchData
- インターフェースで入出力を明確化
- async/await でエラーハンドリングを追加
TypeScript特有のベストプラクティスまとめ
TypeScript 特有のベストプラクティス
anyを避け、unknownを使用する- 適切な型定義を行う
- ジェネリクスで再利用可能なコードを書く
- ユーティリティ型を活用する
- 型の絞り込みで安全性を高める
as constでリテラル型を活用- 網羅性チェックで漏れを防ぐ