TypeScript ベストプラクティス2026年版 — 現場で差がつく15のルール


TypeScriptを「なんとなく」使っていませんか?型定義を適当にanyで済ませたり、せっかくの型安全性を活かせていないコードを見かけることがよくあります。この記事では、2026年の現場で実践すべきTypeScriptのベストプラクティス15選を、具体例とともに解説します。

1. anyを使わず、unknownかneverを使う

❌ 悪い例

function processData(data: any) {
  return data.value.toString(); // data.valueが存在する保証がない
}

✅ 良い例

function processData(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    const value = (data as { value: unknown }).value;
    if (typeof value === 'string' || typeof value === 'number') {
      return value.toString();
    }
  }
  throw new Error('Invalid data format');
}

理由: anyは型チェックを完全に無効化します。unknownを使うことで、型ガードを強制し、安全なコードを書けます。

2. strictモードは常にtrueに設定

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "exactOptionalPropertyTypes": true
  }
}

strict: trueは以下のオプションをすべて有効化します。

  • noImplicitAny - 暗黙のanyを禁止
  • strictNullChecks - null/undefinedチェックを厳格化
  • strictFunctionTypes - 関数の型チェックを厳格化
  • strictBindCallApply - bind/call/applyの型チェック
  • strictPropertyInitialization - クラスプロパティの初期化チェック
  • noImplicitThis - thisの暗黙的なanyを禁止

2026年の新規プロジェクトでは、これらに加えてnoUncheckedIndexedAccessも有効化しましょう。

3. 型アサーション(as)は最小限に

❌ 悪い例

const user = await fetchUser() as User;
const element = document.getElementById('app') as HTMLDivElement;

✅ 良い例

// ランタイムバリデーションと組み合わせる
import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

async function fetchUser(): Promise<User> {
  const response = await fetch('/api/user');
  const data = await response.json();
  return UserSchema.parse(data); // ランタイムで検証
}

// 型ガードを使う
const element = document.getElementById('app');
if (!(element instanceof HTMLDivElement)) {
  throw new Error('Element is not a div');
}
// ここからelementはHTMLDivElement型

理由: 型アサーションはコンパイラを「信じてくれ」と説得するだけで、実際の値を検証しません。zodやyupなどのバリデーションライブラリと組み合わせることで、型とランタイムの両方で安全性を保証できます。

4. ユニオン型とリテラル型を活用

❌ 悪い例

function setStatus(status: string) {
  // statusに何を渡してもコンパイルエラーにならない
}
setStatus('RUNNING'); // OK
setStatus('running'); // OK(本当はNG)
setStatus('typo'); // OK(本当はNG)

✅ 良い例

type Status = 'PENDING' | 'RUNNING' | 'SUCCESS' | 'FAILED';

function setStatus(status: Status) {
  // statusには4つの値しか入らない
}
setStatus('RUNNING'); // OK
setStatus('running'); // コンパイルエラー
setStatus('typo'); // コンパイルエラー

さらに、判別可能なユニオン型(Discriminated Union)を使うと型安全性が向上します。

type Result<T> =
  | { success: true; data: T }
  | { success: false; error: string };

function handleResult<T>(result: Result<T>) {
  if (result.success) {
    console.log(result.data); // dataにアクセス可能
    // result.error; // エラー: successがtrueの時errorは存在しない
  } else {
    console.error(result.error); // errorにアクセス可能
    // result.data; // エラー: successがfalseの時dataは存在しない
  }
}

5. オプショナルチェイニングとNullish Coalescing

// オプショナルチェイニング (?.)
const userName = user?.profile?.name;

// Nullish Coalescing (??)
const displayName = userName ?? 'Guest';

// 組み合わせ
const email = user?.contact?.email ?? 'no-email@example.com';

注意: ||ではなく??を使う理由

const count = 0;
console.log(count || 10); // 10 (0はfalsyなので10になる)
console.log(count ?? 10); // 0  (0はnullでもundefinedでもないので0のまま)

6. ジェネリクスで再利用性を高める

❌ 悪い例

function getFirstString(arr: string[]): string | undefined {
  return arr[0];
}

function getFirstNumber(arr: number[]): number | undefined {
  return arr[0];
}

✅ 良い例

function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

const first = getFirst([1, 2, 3]); // number | undefined
const firstStr = getFirst(['a', 'b']); // string | undefined

より実践的な例:

// API responseのラッパー
type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);
  return response.json();
}

// 使用例
type User = { id: number; name: string };
const userResponse = await fetchApi<User>('/api/user');
// userResponse.dataはUser型

7. Readonly型で不変性を保証

type User = {
  readonly id: number;
  name: string;
};

const user: User = { id: 1, name: 'Tanaka' };
user.name = 'Suzuki'; // OK
user.id = 2; // エラー: Cannot assign to 'id' because it is a read-only property

配列やオブジェクト全体をreadonlyにする:

const config: Readonly<{ apiKey: string; endpoint: string }> = {
  apiKey: 'xxx',
  endpoint: 'https://api.example.com',
};

const items: ReadonlyArray<string> = ['apple', 'banana'];
items.push('orange'); // エラー: Property 'push' does not exist

8. ユーティリティ型を使いこなす

Partial - すべてのプロパティをオプショナルに

type User = {
  id: number;
  name: string;
  email: string;
};

function updateUser(id: number, updates: Partial<User>) {
  // updatesは{ name?: string; email?: string; id?: number }
}

updateUser(1, { name: 'New Name' }); // emailを渡さなくてもOK

Pick - 特定のプロパティだけ抽出

type UserPublicInfo = Pick<User, 'id' | 'name'>;
// { id: number; name: string }

Omit - 特定のプロパティを除外

type UserWithoutId = Omit<User, 'id'>;
// { name: string; email: string }

Record - キーと値の型を指定したオブジェクト

type Role = 'admin' | 'user' | 'guest';
type Permissions = Record<Role, string[]>;

const permissions: Permissions = {
  admin: ['read', 'write', 'delete'],
  user: ['read', 'write'],
  guest: ['read'],
};

9. 型ガードで絞り込み

// typeof型ガード
function processValue(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase(); // valueはstring型
  }
  return value.toFixed(2); // valueはnumber型
}

// instanceof型ガード
function handleError(error: Error | string) {
  if (error instanceof Error) {
    console.error(error.message, error.stack);
  } else {
    console.error(error);
  }
}

// カスタム型ガード
type Dog = { type: 'dog'; bark: () => void };
type Cat = { type: 'cat'; meow: () => void };
type Animal = Dog | Cat;

function isDog(animal: Animal): animal is Dog {
  return animal.type === 'dog';
}

function handleAnimal(animal: Animal) {
  if (isDog(animal)) {
    animal.bark(); // animalはDog型
  } else {
    animal.meow(); // animalはCat型
  }
}

10. 非Nullアサーション演算子(!)は避ける

❌ 悪い例

const user = users.find(u => u.id === 1);
console.log(user!.name); // userが見つからなかったらランタイムエラー

✅ 良い例

const user = users.find(u => u.id === 1);
if (user) {
  console.log(user.name);
} else {
  console.error('User not found');
}

// または
const user = users.find(u => u.id === 1);
console.log(user?.name ?? 'Unknown');

11. インターフェースとタイプエイリアスの使い分け

基本方針:

  • 拡張性が必要なオブジェクト型interface
  • ユニオン・プリミティブ・タプルtype
// interface - 拡張可能
interface User {
  id: number;
  name: string;
}

interface Admin extends User {
  permissions: string[];
}

// type - ユニオンやプリミティブ
type Status = 'active' | 'inactive' | 'pending';
type ID = string | number;
type Point = [number, number];

両方できることも多いですが、チームで統一ルールを決めましょう。

12. 関数のオーバーロード

複雑な関数シグネチャは型安全に定義できます。

// オーバーロードシグネチャ
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'span'): HTMLSpanElement;
function createElement(tag: 'a'): HTMLAnchorElement;

// 実装シグネチャ
function createElement(tag: string): HTMLElement {
  return document.createElement(tag);
}

const div = createElement('div'); // HTMLDivElement型
const span = createElement('span'); // HTMLSpanElement型

13. Mapped TypesとConditional Typesで高度な型操作

// Mapped Types - オブジェクトの各プロパティを変換
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type User = { id: number; name: string };
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null }

// Conditional Types - 条件に応じて型を変える
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<42>; // false

// 実用例: 非同期関数の戻り値を取得
type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R>
  ? R
  : never;

async function fetchUser() {
  return { id: 1, name: 'Tanaka' };
}

type User = AsyncReturnType<typeof fetchUser>;
// { id: number; name: string }

14. barrel exportsで整理されたAPI

// models/index.ts(barrel export)
export type { User } from './user';
export type { Post } from './post';
export type { Comment } from './comment';

// 使う側
import { User, Post, Comment } from '@/models';

注意: barrel exportsは便利ですが、バンドルサイズが増える可能性があります。Viteやesbuildなど、tree-shakingに対応したバンドラーを使いましょう。

15. TypeScript向けの開発ツールを活用

おすすめツール

DevToolBox (https://devtoolbox.app) TypeScript開発者向けの無料Webツール集。型定義の変換、JSON→TypeScript型生成、正規表現テスターなど、現場で役立つツールが揃っています。

ts-node - TypeScriptを直接実行

npm install -D ts-node
npx ts-node script.ts

tsx - 高速なTypeScript実行環境(2026年のデファクトスタンダード)

npm install -D tsx
npx tsx script.ts

tsc-watch - 変更を監視して自動コンパイル

npm install -D tsc-watch
npx tsc-watch --onSuccess "node dist/index.js"

ts-prune - 使われていないexportを検出

npx ts-prune

まとめ

2026年のTypeScriptベストプラクティス15選をおさらいします。

  1. anyを避けunknownを使う
  2. strict: trueで始める
  3. 型アサーションを最小化
  4. ユニオン型とリテラル型を活用
  5. ?.??を使いこなす
  6. ジェネリクスで再利用性向上
  7. Readonlyで不変性を保証
  8. ユーティリティ型を使う
  9. 型ガードで型を絞り込む
  10. !演算子を避ける
  11. interfaceとtypeを使い分ける
  12. 関数オーバーロードで正確な型定義
  13. Mapped/Conditional Typesで高度な型操作
  14. barrel exportsで整理
  15. 開発ツールを活用

これらのプラクティスを実践することで、型安全性が高く、保守しやすいTypeScriptコードが書けるようになります。最初はすべてを完璧に実践する必要はありません。プロジェクトに合わせて、段階的に取り入れていきましょう。

TypeScript開発に役立つツールを探しているなら、DevToolBoxもチェックしてみてください。JSON to TypeScript変換や型定義の整形など、開発効率を上げるツールが揃っています。