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選をおさらいします。
anyを避けunknownを使うstrict: trueで始める- 型アサーションを最小化
- ユニオン型とリテラル型を活用
?.と??を使いこなす- ジェネリクスで再利用性向上
Readonlyで不変性を保証- ユーティリティ型を使う
- 型ガードで型を絞り込む
!演算子を避ける- interfaceとtypeを使い分ける
- 関数オーバーロードで正確な型定義
- Mapped/Conditional Typesで高度な型操作
- barrel exportsで整理
- 開発ツールを活用
これらのプラクティスを実践することで、型安全性が高く、保守しやすいTypeScriptコードが書けるようになります。最初はすべてを完璧に実践する必要はありません。プロジェクトに合わせて、段階的に取り入れていきましょう。
TypeScript開発に役立つツールを探しているなら、DevToolBoxもチェックしてみてください。JSON to TypeScript変換や型定義の整形など、開発効率を上げるツールが揃っています。