Propsの「うっかり書き換え」を封じろ:TypeScriptとreadonlyが守る堅牢なReactコンポーネント
現場でコードをレビューしていると、たまに見かけるんだ。「なぜここでPropsを直接いじってしまったのか?」という光景がね。
Reactの基本原則として「Propsは読み取り専用である」というのは、公式ドキュメントでも耳にタコができるほど繰り返されている。でも、人間というのは脆い生き物だ。深夜のデバッグ中に「ちょっとだけ値を加工したい」という誘惑に負け、`props.user.name = “Guest”` なんてコードを書いてしまった経験、君にもないだろうか?
今日は、TypeScriptの力を借りて、その「誘惑」を物理的に断ち切る方法について、現場の知見を交えて話そうと思う。
なぜPropsの書き換えが「禁忌」なのか
そもそも、なぜReactはPropsの書き換えを嫌うのか。
Reactは「UI = f(state, props)」という純粋関数の考え方に基づいている。もしコンポーネント内でPropsを直接書き換えてしまうと、Reactのレンダーサイクルが破壊される。親コンポーネントは自分が渡したデータが変更されたことに気づけず、結果としてUIの不整合や、予測不能なバグが生まれる。
ブラウザの裏側では、Reactは仮想DOMを用いて変更を検知している。Propsという「親から受け取った読み取り専用の指示書」を勝手に書き換えてしまうことは、Reactの再レンダリングの仕組みを根底から覆す行為なんだ。
TypeScriptによる強制的な「読み取り専用」化
TypeScriptを使っているなら、`readonly` 修飾子を活用して、コンパイル時点で不正を弾くのがプロの流儀だ。
以下に、実務でそのまま使える、堅牢なProps定義のパターンを示すよ。
// 型定義に readonly を付与して、不変性を保証する
interface UserProfileProps {
readonly id: string;
readonly name: string;
readonly tags: readonly string[]; // 配列の中身まで保護するために重要
}
export const UserProfile = ({ id, name, tags }: UserProfileProps) => {
// ここで name = “new name” と代入しようとすると、
// TypeScriptは即座にエラーを出して君を止めてくれる。
return (
{name}
-
{tags.map((tag) =>
- {tag}
)}
);
};
「罠」は深いところに潜んでいる:Deep Readonlyの重要性
さっきのサンプルコードで、`tags: readonly string[]` と書いたことに気づいたかな?
実は、単に `readonly` をプロパティにつけるだけでは、オブジェクトや配列の「中身」までは保護しきれない。`const` と同じで、参照先を書き換えることはできなくても、中のプロパティを変更するメソッド(`push` など)は実行できてしまうことがあるんだ。
これを完全に防ぐには、TypeScriptの組み込み型 `Readonly
// ユーティリティ型で再帰的に読み取り専用にする(現場でよく使うテクニック)
type DeepReadonly
readonly [P in keyof T]: T[P] extends object ? DeepReadonly
};
interface ConfigProps {
readonly settings: {
readonly theme: string;
};
}
// これなら settings.theme = ‘dark’ と書こうとしてもエラーになる
チームへの提言:なぜ「厳格さ」が必要なのか
「そこまでガチガチに固めなくても、気をつければいいのでは?」と考える人もいるかもしれない。だが、フロントエンド開発は常に「複数人での共同作業」だ。
君が書いたコンポーネントを、半年後に全く別のメンバーが触るかもしれない。その時、この `readonly` があるかないかで、バグの混入率は劇的に変わる。「コンパイルが通る=仕様を満たしている」という信頼関係をコードで構築することこそが、シニアエンジニアの仕事なんだ。
まとめ:次にやるべきこと
1. 既存のProps型定義を見直せ: 全てのインターフェースに `readonly` を付ける癖をつけよう。
2. 配列やオブジェクトに注意せよ: `readonly` の効力がどこまで及ぶのかを意識し、必要なら `DeepReadonly` を導入しよう。
3. Linterを頼れ: `eslint-plugin-react` や `@typescript-eslint` のルールを活用して、機械的にPropsの不変性を担保する環境を整えよう。
Reactは自由度が高いフレームワークだ。だからこそ、こうした「制約」を自分たちで意図的に設けることで、初めて大規模で保守性の高いアプリケーションが作れるようになる。
君のコードが、次に触る誰かにとっても「安心して触れる資産」になることを期待しているよ。また何か詰まったら、いつでも聞きに来てくれ。

コメント