【実務・中級編】 Propsの読み取り専用制約とreadonly修飾子 – React実践ガイド

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` を使うか、必要に応じて `DeepReadonly` 型を自作するのが現場でのベストプラクティスだ。

// ユーティリティ型で再帰的に読み取り専用にする(現場でよく使うテクニック)
type DeepReadonly = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P];
};

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は自由度が高いフレームワークだ。だからこそ、こうした「制約」を自分たちで意図的に設けることで、初めて大規模で保守性の高いアプリケーションが作れるようになる。

君のコードが、次に触る誰かにとっても「安心して触れる資産」になることを期待しているよ。また何か詰まったら、いつでも聞きに来てくれ。

コメント

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