【実務・中級編】 PickとOmitを用いたPropsのフィルタリング – React実践ガイド

こんにちは。君もそろそろ「単に動くだけのReactコード」から卒業し、大規模なアプリケーションを破綻させずにスケールさせるための設計力に直面するころだな。

実務でコードベースが肥大化してくると、一番最初に頭を悩ませるのが「コンポーネント間のPropsの重複と型定義のメンテ地獄」だ。特に、既存のUIコンポーネント(例えば、弊社の誇る強固な`Button`)のPropsを少しだけ改変して、新しい別のコンポーネントを作りたい時、君はどうしている? まさか、イチから型を書き直したり、コピペして手動で一部を書き換えたりしてはいないだろうね?

今回は、TypeScriptのユーティリティ型である `Pick` と `Omit` を駆使して、Propsのフィルタリングをスマートに行い、DRY(Don’t Repeat Yourself)原則を極限まで守り抜くための実践的なテクニックを授けよう。

—

なぜPropsの「流用とフィルタリング」が必要なのか?

実務の現場を思い出してほしい。よくある要件として、「基本のボタンコンポーネントはあるけれど、特定の画面専用のスペシャルなボタンを作りたい。ただし、その画面ではクリック時のイベント(`onClick`)の挙動を完全にラップしたいから、外部からの直接の `onClick` は受け付けたくないんだよな……」といったシチュエーションがある。

ここで、ベースとなる `ButtonProps` をそのまま流用しつつ、不要なプロパティを削ぎ落としたり(`Omit`)、特定のプロパティだけを厳選して抽出したり(`Pick`)する技術が必須になる。

これを怠って、コンポーネントごとに似たような型をバラバラに定義し始めると、ベースの仕様変更が入った瞬間に型定義の同期が漏れ、あちこちでバグが爆誕する。フロントエンド・アーキテクトとして、それは絶対に防がなければならない。

—

PickとOmitの基本メカニズム(裏側で何が起きているか)

TypeScriptの `Pick` と `Omit` は、どちらも既存の型から新しい型を「外科手術的に」作り出すためのジェネリック型だ。ブラウザが実行時に何か特別な処理をするわけではないが、コンパイルタイム(TypeScriptの型チェック時)において、エディタの補完と型安全性を担保するための最強の武器となる。

  • `Pick`: 型 `T` の中から、プロパティ `K` のみを抽出する。(必要なものだけ摘み取る)
  • `Omit`: 型 `T` の中から、プロパティ `K` 以外を抽出する。(不要なものを除外する)

これらは内部的にはマップ型(Mapped Types)と条件付き型(Conditional Types)の合わせ技で実装されている。TypeScriptのエンジンは、これらの型を受け取ると、既存のオブジェクトの構造から指定されたキーを安全にフィルタリングし、全く新しいオブジェクト型をメモリ上に構築するのだ。

—

現場で即戦力になる実装パターン

百聞は一見にしかず。実際のコードベースを想定したサンプルを見ていこう。
今回は、汎用的な `BaseButton` コンポーネントをベースにして、特定の制約を持たせた `CardButton` を作成するシナリオだ。

import React from ‘react’;

// ==========================================
// 1. ベースとなるコンポーネントの型と実装
// ==========================================
export interface BaseButtonProps {
/ ボタンの中に表示するラベル /
label: string;
/ ボタンの視覚的なバリエーション /
variant: ‘primary’ | ‘secondary’ | ‘danger’;
/ クリック時のイベントハンドラ /
onClick: (event: React.MouseEvent) => void;
/ 無効状態かどうか /
disabled?: boolean;
/ 内部的なトラッキングID(ビジネスロジック用) /
trackingId: string;
}

export const BaseButton: React.FC = ({
label,
variant,
onClick,
disabled = false,
trackingId,
}) => {
return (

);
};

// ==========================================
// 2. Omitを用いたプロパティの除外(ラッピング)
// ==========================================
/

  • 【ユースケース: Omit】
  • カード内に配置する専用ボタンを作りたいとする。
  • このカードボタンでは、外部から勝手に `trackingId` を上書きされたくない(内部で自動採番するため)。
  • また、デザインの都合上、`variant` は常に ‘primary’ に固定したい。
  • BaseButtonProps から ‘trackingId’ と ‘variant’ を除外した型を定義する。

/
export type CardButtonProps = Omit;

export const CardButton: React.FC = (props) => {
// 内部で自動的にトラッキングIDを生成・付与し、variantを固定してBaseButtonに委譲する
const handleCardButtonClick = (e: React.MouseEvent) => {
// 独自のトラッキング処理などをここに挟める
console.log(‘Card button clicked, internal tracking executed.’);
props.onClick(e);
};

return (

);
};

// ==========================================
// 3. Pickを用いたプロパティの抽出(限定公開)
// ==========================================
/

  • 【ユースケース: Pick】
  • さらに、最小限の機能だけを持つミニマルなバッジ用ボタンを別のチームから要求されたとする。
  • このボタンでは、ラベルとクリックイベントしか必要なく、variantやdisabled、trackingIdなどは一切不要。
  • BaseButtonProps から ‘label’ と ‘onClick’ のみを抽出する。

/
export type MinimalButtonProps = Pick;

export const MinimalButton: React.FC = ({ label, onClick }) => {
// 内部的にデフォルトのvariantやダミーのtrackingIdを補完してBaseButtonを呼び出す
return (

);
};

—

シニアが教える!実務におけるベストプラクティスと罠

この `Pick` と `Omit`、一見すると万能に見えるが、実務で雑に使い倒すと後々痛い目を見る。シニアとして、いくつか現場の教訓を共有しておこう。

1. 「コンポーネントの肥大化」のサインとして受け取る

`Omit` を使ってあまりにも多くのプロパティを削っている場合(例: 20個あるPropsのうち18個を `Omit` で消すようなケース)、そのベースとなるコンポーネント自体の設計が間違っている可能性が高い。単一責任の原則(SRP)に立ち返り、コンポーネントをアトミックに分割すべきだ。

2. HTMLのネイティブ要素を拡張・制限する場合の注意

Reactでよくあるのが、`React.ComponentPropsWithoutRef<'button'>` から特定のイベントハンドラを除外するパターンだ。

type CustomInputProps = Omit, ‘onChange’> & {
// 独自にカスタムしたonChangeを定義
onChange: (value: string) => void;
};

このようにネイティブの型と組み合わせることで、開発体験が劇的に向上する。

—

まとめ

`Pick` と `Omit` は、単なるTypeScriptの機能ではない。「コンポーネント間の関係性を美しく保ち、変更に強いアーキテクチャを構築するためのデザインパターン」そのものだ。

DRY原則を意識し、型定義の重複を排除していくことで、君の書くReactコードベースは圧倒的にメンテナンスしやすくなり、チームメンバーからも一目置かれる存在になるはずだ。

今日の帰り道、自分が過去に書いたコンポーネントの型定義を見返して、「あ、ここ `Omit` 使えばもっと綺麗になるな」と思う箇所を探してみてほしい。その積み重ねが、君を真のフロントエンド・スペシャリストへと導く。さて、次のタスクに取り掛かろうか。

コメント

タイトルとURLをコピーしました