こんにちは。君もそろそろ「単に画面を組み立てられる」だけのジュニアを卒業し、コンポーネント設計の美しさと現実のトレードオフに頭を悩ませる中級のフェーズに入った頃だな。
今回は、React開発において避けて通れない、しかし雑に扱いがちな「Propsのレスト・スプレッド構文(`{…props}`)」について話をしよう。
「親から受け取ったPropsを、そのまま子やHTML要素にスプレッドして流すだけだろ? 何も難しくないよ」と思ったそこの君。ちょっと待ってほしい。その「なんとなく便利なパススルー」のせいで、将来的に型エラーの温床になったり、意図しないDOM属性がHTMLにリークしてコンソールが警告まみれになったり、パフォーマンスの最適化を阻害する「負債」を生んでいないだろうか?
今日は、シニアの視点から、この構文の裏側の仕組みから実務で絶対に外せないベストプラクティスまで、泥臭い知見を含めて徹底的に紐解いていこう。
—
1. そもそも `{…props}` の裏側で何が起きているのか?
JavaScript(ひいてはV8などのブラウザのJSエンジン)の視点から、スプレッド構文 `{…props}` が何をしているかをおさらいしておこう。
JSのオブジェクトにおけるスプレッド構文は、「新しいオブジェクトをシャローコピー(浅い複製)し、列挙可能なプロパティを展開する」という操作だ。
const baseProps = { id: ‘btn-1’, className: ‘primary’, onClick: handleClick };
const extendedProps = { …baseProps, disabled: true };
Reactのコンポーネントモデルにおいて、JSXは最終的に `React.createElement` という関数呼び出しにトランスパイルされる。親から渡されたPropsオブジェクトを `{…props}` でそのまま子コンポーネントやネイティブのHTML要素(`` など)に渡すとき、Reactは内部の仮想DOM(Fiberツリー)の構築時に、そのオブジェクトのプロパティをイテレートしてPropsとして子へバインドしている。
この仕組みは非常に強力で、コンポーネントの「ラッパー(Wrapper)」や「HOC(Higher-Order Component)的アプローチ」を作る際にはなくてはならないものだ。しかし、「何が流れてくるか分からない」というブラックボックスを生み出す諸刃の剣でもある。
—
2. 現場で使える!エレガントな「Props転送」のサンプルコード
百聞は一見にしかず。実務でそのまま使える、堅牢で型安全な「ボタンコンポーネント」のコードを見てみよう。TypeScriptを用いた、現代のReact開発における標準的な実装だ。
import React, { ComponentPropsWithoutRef } from ‘react’;
// 1. 独自に拡張したいPropsを定義
type CustomButtonVariant = ‘primary’ | ‘secondary’ | ‘danger’;
export interface ButtonProps extends ComponentPropsWithoutRef<'button'> {
/ ボタンの見た目のバリエーション /
variant?: CustomButtonVariant;
/ ローディング中のスピナーを表示するかどうか /
isLoading?: boolean;
}
export const Button: React.FC
children,
variant = ‘primary’,
isLoading = false,
className = ”,
disabled,
…restProps // <-- ここで残りのProps(onClickやaria-など)をレスト構文で回収する
}) => {
// スタイルをバリエーションに応じて動的に切り替えるロジック
const baseStyles = ‘px-4 py-2 rounded font-medium transition-colors focus:outline-none’;
const variantStyles = {
primary: ‘bg-blue-600 text-white hover:bg-blue-700’,
secondary: ‘bg-gray-200 text-gray-800 hover:bg-gray-300’,
danger: ‘bg-red-600 text-white hover:bg-red-700’,
}[variant];
return (
);
};
このコードの優れたポイント
1. `ComponentPropsWithoutRef<'button'>` の活用: ネイティブの `
—
3. 実務でハマりがちな「アンチパターン」とシニアからの助言
この `{…props}` 構文、便利すぎるゆえに現場でやらかしがちな罠がある。いくつか代表的なものを共有しておこう。
罠その1:Propsの「上書き順序(シャドーイング)」の事故
スプレッド構文を書く際、プロパティを記述する「順番」が極めて重要だ。
// ❌ 危険な例:親から渡された className が、こちらのデフォルトで完全に上書きされて消えてしまう
// 〇 正しい例:後からスプレッドすることで、親から渡された className や onClick を適切にマージ・上書きできるようにする
JSのオブジェクトスプレッドは「後勝ち(オーバーライド)」だ。どのようなデータフローを想定しているのか、スプレッドの位置には細心の注意を払おう。
罠その2:不要な再レンダリングの誘発
親コンポーネントが毎回インラインのオブジェクトや関数を生成して `{…props}` で子に流し込むと、子コンポーネント側で `React.memo` を使っていても、Propsの参照が毎回変わるため最適化が破壊される。
「とりあえず全部スプレッドしとけ」という雑な設計は、アプリがスケールしたときのパフォーマンス劣化(カクつき)の大きな原因になる。本当にその全Propsが必要なのか、時々立ち止まって疑う勇気を持とう。
—
4. まとめ
Propsのレスト・スプレッド構文は、Reactのコンポーネント合成能力を何倍にも高めてくれる強力なツールだ。しかし、それは「型安全な設計」と「DOMへの影響のコントロール」という規律があって初めて活きる。
- 独自の拡張プロパティは事前に分割代入で抜き取る
- ネイティブ要素への転送には `ComponentPropsWithoutRef` などのユーティリティ型を組み合わせる
- スプレッドの順番(オーバーライドの優先順位)を意識する
このあたりを意識するだけで、君が書くコードの品質は一段も二段も跳ね上がるはずだ。
現場で「お、こいつの書くコード、綺麗だな」と周囲を唸らせてやってほしい。応援しているぞ!

コメント