Back

知っておくべき5つのTypeScript Utility Types

知っておくべき5つのTypeScript Utility Types

TypeScriptの型システムは、既存の型を書き直すことなく変換する組み込みヘルパーであるutility typesを発見したときに真の力を発揮します。インターフェースの重複に悩んだり、型の変更に苦労したことがあるなら、これら5つの必須utility typesがTypeScriptコードの書き方を変えるでしょう。

重要なポイント

  • Utility typesは既存の型を書き直すことなく変換し、コードの重複を削減します
  • Partialはすべてのプロパティをオプションにし、更新操作に最適です
  • Requiredはすべてのプロパティを強制し、完全なデータ構造を保証します
  • PickとOmitはプロパティの選択や除外により、焦点を絞ったインターフェースの作成を支援します
  • Readonlyは意図しない変更を防ぎ、不変データパターンに不可欠です

TypeScript Utility Typesとは何か、なぜ重要なのか

Utility typesは、既存の型を変換して新しい型を構築するTypeScriptの事前構築されたジェネリック型です。インターフェースのバリエーションを手動で作成する代わりに、これらのユーティリティを使用して特定の変更を加えた新しい型を導出できます。これによりコードがより保守しやすくなり、型の重複が減少します。

実際の問題を解決する5つの基本的なutility typesを探ります:PartialRequiredPickOmitReadonlyです。それぞれがTypeScript開発で日常的に遭遇する特定のシナリオに対応しています。

Partial<T>:TypeScriptですべてのプロパティをオプションにする

Partial utility typeは、型のすべてのプロパティをオプションに変換し、特定のフィールドのみを変更する更新操作に最適です。

フォーム更新でPartialを使用するタイミング

ユーザーがオブジェクト全体を送信することなく個別のフィールドを変更できるユーザープロフィール更新を考えてみましょう:

interface User {
  id: string;
  name: string;
  email: string;
  avatar: string;
  lastLogin: Date;
}

function updateUserProfile(userId: string, updates: Partial<User>) {
  // 提供されたフィールドのみを更新
  return api.patch(`/users/${userId}`, updates);
}

// 使用例 - emailのみを更新
updateUserProfile("123", { email: "new@email.com" });

実際のコードでのPartial型の例

Reactの状態更新はPartialから大きな恩恵を受けます:

const [user, setUser] = useState<User>(initialUser);

const updateUser = (updates: Partial<User>) => {
  setUser(prev => ({ ...prev, ...updates }));
};

// 特定のフィールドのみを更新
updateUser({ name: "Jane Doe", avatar: "new-avatar.jpg" });

Required<T>:TypeScriptですべてのプロパティを強制する

RequiredはPartialの反対を行います。すべてのオプションプロパティを必須にし、完全なデータ構造を保証します。

オプションプロパティを必須プロパティに変換する

これは設定の検証において非常に有用です:

interface DatabaseConfig {
  host?: string;
  port?: number;
  username?: string;
  password?: string;
}

// 接続前にすべての設定値が存在することを保証
type ValidatedConfig = Required<DatabaseConfig>;

function connectDatabase(config: ValidatedConfig) {
  // すべてのプロパティの存在が保証される
  return createConnection(config);
}

Required型の実践

フォーム送信では多くの場合、完全なデータが必要です:

interface RegistrationForm {
  username?: string;
  email?: string;
  password?: string;
  terms?: boolean;
}

type CompleteRegistration = Required<RegistrationForm>;

function submitRegistration(data: CompleteRegistration) {
  // すべてのフィールドが入力されている必要がある
  api.post('/register', data);
}

Pick<T, K>:型から特定のプロパティを選択する

Pickは指定されたプロパティのみを選択して新しい型を作成し、焦点を絞ったインターフェースの作成に理想的です。

Pickで焦点を絞った型のサブセットを作成する

特定のコンポーネントに必要なもののみを抽出します:

interface Article {
  id: string;
  title: string;
  content: string;
  author: string;
  publishedAt: Date;
  tags: string[];
}

// カードコンポーネントにはこれらのフィールドのみが必要
type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;

function ArticleCard({ article }: { article: ArticlePreview }) {
  return (
    <div>
      <h3>{article.title}</h3>
      <p>by {article.author}</p>
    </div>
  );
}

Pick vs Omit:適切なアプローチの選択

少数のプロパティが必要な場合はPickを、少数のプロパティを除外する場合はOmitを使用します:

// Pick:大きな型から2-3のプロパティが必要な場合
type UserSummary = Pick<User, 'id' | 'name'>;

// Omit:少数を除いてほとんどのプロパティが必要な場合
type PublicUser = Omit<User, 'password'>;

Omit<T, K>:TypeScript型からプロパティを除外する

Omitは指定されたプロパティを除外して型を作成し、機密情報や内部フィールドの削除に最適です。

Omitで機密データを削除する

APIレスポンスで機密情報を保護します:

interface UserAccount {
  id: string;
  email: string;
  password: string;
  creditCard: string;
  publicProfile: boolean;
}

// パブリックAPIから機密フィールドを削除
type PublicUserData = Omit<UserAccount, 'password' | 'creditCard'>;

function getPublicProfile(userId: string): Promise<PublicUserData> {
  return api.get(`/users/${userId}/public`);
}

Omit Utility Typeのベストプラクティス

他のユーティリティと組み合わせて強力なパターンを作成します:

// 内部フィールドを削除し、残りをオプションにする
type UserUpdatePayload = Partial<Omit<User, 'id' | 'createdAt'>>;

Readonly<T>:TypeScriptで不変型を作成する

Readonlyはすべてのプロパティを不変にし、意図しない変更を防ぎ、データの整合性を強制します。

Readonlyで意図しない変更を防ぐ

状態管理と設定において不可欠です:

interface AppConfig {
  apiEndpoint: string;
  version: string;
  features: string[];
}

type ImmutableConfig = Readonly<AppConfig>;

const config: ImmutableConfig = {
  apiEndpoint: "https://api.example.com",
  version: "1.0.0",
  features: ["auth", "payments"]
};

// TypeScriptエラー:'apiEndpoint'は読み取り専用プロパティのため代入できません
config.apiEndpoint = "https://new-api.com"; // ❌

ReadonlyプロパティとReact Props

propsが変更されないことを保証します:

type UserCardProps = Readonly<{
  user: User;
  onSelect: (id: string) => void;
}>;

function UserCard({ user, onSelect }: UserCardProps) {
  // propsはコンポーネント内で不変
  return <div onClick={() => onSelect(user.id)}>{user.name}</div>;
}

高度なパターンのための複数のUtility Typesの組み合わせ

Utility typesは組み合わせることでさらに強力になります:

interface DatabaseRecord {
  id: string;
  createdAt: Date;
  updatedAt: Date;
  deletedAt?: Date;
  data: Record<string, unknown>;
}

// 新しいレコード用の型を作成:タイムスタンプなし、部分的なデータ
type NewRecord = Omit<DatabaseRecord, 'id' | 'createdAt' | 'updatedAt'> & {
  data: Partial<DatabaseRecord['data']>;
};

// 更新型:ID以外すべてオプション
type UpdateRecord = Partial<Omit<DatabaseRecord, 'id'>> & Pick<DatabaseRecord, 'id'>;

まとめ

これら5つのutility types(PartialRequiredPickOmitReadonly)は、効果的なTypeScript開発の基盤を形成します。これらは反復的な型定義を排除し、型安全性を強制し、コードをより保守しやすくします。

これらのパターンをコードベースに組み込み始めましょう。更新操作にはPartialから始め、安全なパブリックインターフェースの作成にはOmitを使用し、バグを防ぐためにReadonlyを活用しましょう。慣れてきたら、これらを組み合わせてアプリケーションのニーズに完璧に合致する洗練された型変換を作成しましょう。

よくある質問

はい、utility typesをネストして複雑な変換を作成できます。例えば、PartialとOmitは組み合わせることで、特定のフィールドを除外しながら他をオプションにする更新ペイロードを作成するのに適しています。

いいえ、utility typesは純粋にコンパイル時の構成要素です。TypeScriptはコンパイル中にすべての型情報を削除するため、utility typesは実行時のオーバーヘッドがゼロです。

インターフェース拡張はプロパティを追加して新しい型を作成しますが、utility typesは既存の型を変換します。Utility typesは、制御できない型の変更や既存の型のバリエーション作成において、より柔軟性を提供します。

はい、TypeScriptではmapped types、conditional types、template literal typesを使用してカスタムutility typesを作成できます。組み込みユーティリティから始めて、繰り返しパターンを特定したときにカスタムのものを作成しましょう。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay