404 Not Found

404 Not Found


nginx

TypeScript のベストプラクティス

文法を習得するのはほんの始まりに過ぎません。優れたTypeScriptを書くためには、一連のベストプラクティスに従う必要があります。このレッスンでは、実際のプロジェクトから得られた最も有益な知見と原則をまとめています。

1. 命名規則

(1) 型の命名

カテゴリ 仕様
インターフェース パスカルケース UserService, ApiResponse
タイプエイリアス パスカルケース Status, EventHandler
汎用パラメータ 1文字またはパスカルケース T, K, TItem
列挙型 パスカルケース(値は大文字の場合もある) HttpStatus, COLOR_RED
列挙型の要素 パスカルケース HttpStatus.Ok

(2) ファイル名の付け方

種類 仕様
通常モジュール キャメルケース userService.ts
ファイルの種類 パスカルケース UserController.ts
宣言ファイル モジュールと同じ名前 lodash.d.ts
テストファイル モジュール名.test userService.test.ts

(3) 輸出仕様

TYPESCRIPT
// ✅ Recommendations——Prioritize naming and exporting
export function addUser(user: User): void { }
export class UserController { }
export type Status = "active" | "inactive";

// ⚠️ Use Default Export with Caution——It's easy to make mistakes when renaming files
export default class UserController { }

// ✅ Library/Framework recommended — both options are available
export class UserController { }
export default UserController;

2. タイプデザインの原則

(1) 原則 1:広さよりも正確さ

TYPESCRIPT
// ❌ Broad——Type information lost
function process(value: any): any { }

// ❌ Slightly better, but still too broad
function process(value: string | number): string | number { }

// ✅ Accurate——Generics Preserve Type Information
function process<T extends string | number>(value: T): T { }

(2) 原則 2:計算するほうが手書きするより良い

TYPESCRIPT
// ❌ Manually maintain two locations——Prone to desynchronization
interface User { id: number; name: string; email: string; }
type UserKeys = "id" | "name" | "email";

// ✅ Inference Based on Source Type——Automatic Synchronization
type UserKeys2 = keyof User;  // "id" | "name" | "email"
type UserValues = User[keyof User];  // number | string

(3) 原則3:継承よりも合成

TYPESCRIPT
// ❌ Deep Inheritance——The Problem with Fragile Base Classes
class BaseEntity { id: number; }
class TimestampedEntity extends BaseEntity { createdAt: Date; }
class FullEntity extends TimestampedEntity { createdBy: string; }

// ✅ Type Combinations——Flexible and decoupled
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithAudit = { createdBy: string; updatedBy: string };

type FullEntity2 = WithId & WithTimestamps & WithAudit;
type SimpleEntity = WithId;  // Customizable Combinations

▶ 例:「any」を型安全なものにリファクタリングする

TYPESCRIPT
// ❌ Before the refactoring——everywhere any,Zero Type Safety
function processRequest(req: any): any {
  let user = req.body.user;       // any
  let result = validate(user);    // any
  return { status: 200, data: result };
}

// ✅ After the refactoring——Type safety at every step
interface User { id: number; name: string; email: string; }
interface Request2 { body: { user: User } }
interface ValidationResult { valid: boolean; errors?: string[] }
interface Response2<T> { status: number; data: T }

function processRequest2(req: Request2): Response2<ValidationResult> {
  let user: User = req.body.user;             // ✅ User Type
  let result: ValidationResult = validate2(user);  // ✅ ValidationResult
  return { status: 200, data: result };
}

function validate2(user: User): ValidationResult {
  if (!user.email.includes("@")) {
    return { valid: false, errors: ["Invalid email address"] };
  }
  return { valid: true };
}

3. anyの代替案

(1) いずれかの代替案として不明

TYPESCRIPT
// ❌ any——Turn off type checking
function process(value: any) {
  return value.toUpperCase();  // Do not check,May crash during runtime
}

// ✅ unknown——You must narrow it first before you can use it.
function process2(value: unknown) {
  if (typeof value === "string") {
    return value.toUpperCase();  // ✅ Safe Use After Narrowing
  }
  throw new Error("Expectations string Type");
}

(2) anyの代替としてのジェネリック

TYPESCRIPT
// ❌ any——Missing Type Information
function first(arr: any[]): any {
  return arr[0];
}

// ✅ Generics——Preserve Type Information
function first2<T>(arr: T[]): T {
  return arr[0];
}

(3) anyの代わりにユニオン型を使用する

TYPESCRIPT
// ❌ any
let value: any;

// ✅ Composite Types——Clearly list the possible types
let value2: string | number | boolean;

(4) any オブジェクトをインデックスシグネチャに置き換える

TYPESCRIPT
// ❌ any Object
let config: any = { host: "localhost" };

// ✅ Index Signature
let config2: Record<string, string | number> = { host: "localhost" };

4. DRYの原則(Don't Repeat Yourself)

(1) 重複を避けるために、keyoftypeof、およびユーティリティ型を使用する

TYPESCRIPT
const THEMES = {
  light: { bg: "#fff", text: "#333" },
  dark: { bg: "#1a1a1a", text: "#e0e0e0" }
} as const;

// Type Inference from Values——No duplicate definitions
type ThemeName = keyof typeof THEMES;  // "light" | "dark"
type ThemeColors = typeof THEMES["light"];  // { readonly bg: "..."; readonly text: "..." }

function getTheme(name: ThemeName): ThemeColors {
  return THEMES[name];
}

(2) マッピング型を用いた一括変換

TYPESCRIPT
interface ApiUser {
  id: number;
  name: string;
  email: string;
  role: string;
}

// No handwriting required——Derivation Using Tool Types
type CreateUserDTO = Omit<ApiUser, "id">;
type UpdateUserDTO = Partial<Omit<ApiUser, "id">>;
type UserSummary = Pick<ApiUser, "id" | "name">;
type UserResponse = Readonly<ApiUser>;

5. 型絞り込みの戦略

(1) できるだけ早い段階で絞り込む

TYPESCRIPT
// ❌ The gap is narrowing——Check each location before use
function process(value: string | number) {
  console.log(value.toString());      // Only shared methods can be used
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  }
  // Further checks will be needed later....
}

// ✅ Narrow as soon as possible——Use directly within the branch
function process2(value: string | number) {
  if (typeof value === "string") {
    // The entire branch is string
    console.log(value.toUpperCase());
    console.log(value.trim());
    return;
  }
  // This must be number
  console.log(value.toFixed(2));
}

(2) カスタム型ガードにおける絞り込みロジックの再利用

TYPESCRIPT
// The Complex Logic Behind Narrowing——Extract as a type guard
function isValidUser(obj: any): obj is User {
  return obj
    && typeof obj.id === "number"
    && typeof obj.name === "string"
    && typeof obj.email === "string";
}

// Reuse in Multiple Places
function processUser(data: unknown) {
  if (isValidUser(data)) {
    console.log(data.name);  // ✅ Type Safety
  }
}

6. チームでの協働に関するガイドライン

(1) 統一された tsconfig 設定

JSON
{
  "compilerOptions": {
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

同じルールがすべてのチームメンバーのIDEに自動的に適用されるため、手動での設定は不要です。

(2) ESLintのTypeScriptルール

JSON
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking"
  ],
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-unnecessary-type-assertion": "error",
    "@typescript-eslint/explicit-function-return-type": "warn"
  }
}

(3) コードレビューのチェックリスト


❓ よくある質問

Q プロジェクト内で any の使用を完全に禁止すべきでしょうか?
A それは現実的ではありません。ESLint の設定を error ではなく no-explicit-any: warn に設定してください。これにより、ごく少数の any の使用は許可されますが、レビューが必要であることを通知します。anyは、型指定のないサードパーティ製ライブラリ、複雑な型変換、迅速なプロトタイピングなどのシナリオでは妥当です。重要なのは、その理由を説明するコメントを付け、置き換えの計画を立てることです。
Q 型定義は別のファイルに置くべきか、それとも使用されるファイル内に置くべきか?
A パブリック/共有の型は types/ または models/ ディレクトリに配置すべきです。単一のファイル内でのみ使用される型は、そのファイル内に直接定義すべきです。ルール:3つ以上のファイルから参照される型は、パブリック型ファイルに抽出してください。それ以外の場合は、使用されているファイル内で定義してください。
Q TypeScriptプロジェクトでESLintを使うべきですか?
A はい。tscは型チェックを担当し、ESLintはコード品質のチェックを担当します。この2つは互いに置き換えるものではなく、補完し合う関係にあります。@typescript-eslintプラグインはTypeScript固有のルール(no-explicit-anyconsistent-type-importsなど)を提供しており、TypeScriptプロジェクトにおける標準的な選択肢となっています。
Q ジェネリック型パラメータが多すぎる場合はどうすればよいですか?
A 型パラメータが3つを超える場合は、以下の点を検討してください:(1) 複数のジェネリックをオブジェクトパラメータに置き換える;(2) 一部の型を別のインターフェースに抽出する;(3) ジェネリックのデフォルト値を使用して、指定が必要なパラメータの数を減らす。型パラメータが多すぎる場合は、通常、抽象化のレベルが不適切であることを示しています。

📖 まとめ

📝 練習問題

  1. 基本演習(難易度 ⭐):自分が書いた、あるいは見たことのある any 型の例を見つけ、それを unknown またはジェネリック型に置き換えてください。置き換え後も機能が同じままであり、コードの型安全性が向上していることを確認してください。
  2. 上級問題(難易度 ⭐⭐):DRYの原則に従って、一連の型定義をリファクタリングしてください。与えられたApiProductインターフェースに基づき、ユーティリティ型を使用して、CreateProductUpdateProductProductSummaryProductResponseの4つの型を導出してください。その際、重複するプロパティを手動で記述してはいけません。
  3. 課題(難易度:⭐⭐⭐):チーム向けのTypeScriptコーディングガイドラインを作成してください。これには、命名規則、anyの代替案、型のインポート規則、推奨されるtsconfigの設定、および推奨されるESLintルールを含めてください。各ルールについて、その根拠と、具体例および反例を併せて提示してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%