【実務・中級編】 Propsの不変性(Immutability) – React実践ガイド

なぜPropsをいじってはいけないのか? Reactの「不変性(Immutability)」をハックする実務的視点

現場でコードレビューをしていると、時折見かけるのが「Propsとして渡されたオブジェクトを直接書き換えてしまう」という実装だ。

「え、これくらいなら動くでしょ?」という声が聞こえてきそうだが、結論から言うと、それはReactというフレームワークの心臓部にナイフを突き立てるような行為だ。なぜ公式がこれほどまでに「Propsは読み取り専用(Read-only)であるべき」と叫ぶのか。その理由を、ブラウザの裏側まで含めて深掘りしよう。

1. 「なぜダメなのか」の裏側:Reactの再レンダリング戦略

ReactがなぜPropsをイミュータブル(不変)に保つことを強制するのか。その理由は、Reactの「差分検知(Reconciliation)」のアルゴリズムにある。

Reactは、コンポーネントが再レンダリングされるべきかどうかを判断するために、「前回のProps」と「今回のProps」を比較する。このとき、Reactは非常に軽量な比較アルゴリズム(Object.isによる参照比較)を使っている。

もし君がPropsとして渡されたオブジェクトを直接書き換えてしまうと、どうなるか?

  • 参照(メモリ上のアドレス)が変わらない: Reactは「お、参照先が同じなら中身も変わってないな」と判断し、本来走るべき再レンダリングをスキップしてしまう。
  • UIの同期が崩壊する: データは変わっているのに画面が更新されないという、デバッグ泣かせの「不整合」が生まれる。

ブラウザの裏側では、オブジェクトのプロパティを書き換えてもメモリ上の参照アドレスは同じだ。Reactは「中身をいちいち深追いして全チェックする」という重い処理を避けることでパフォーマンスを維持している。だからこそ、「オブジェクトの参照を変える(=新しいオブジェクトを作る)」というルールを守ることが、Reactのパフォーマンスと整合性を担保する唯一の鍵になるんだ。

2. 現場で使える「不変性を守る」コーディングテクニック

実務では、「状態の一部だけを更新したい」という場面が頻繁にあるはずだ。そのとき、オブジェクトを破壊せず、かつクリーンに更新するためのベストプラクティスを共有しよう。

アンチパターン:やってはいけない実装

// ❌ 絶対にやってはいけない!Propsの直接変更
const UserProfile = ({ user }) => {
const updateName = () => {
user.name = “新しい名前”; // Reactはこれを検知できない!
};
return ;
};

ベストプラクティス:スプレッド構文を用いたイミュータブルな更新

現場では、スプレッド構文(`…`)を使って「新しいオブジェクト」を生成するのが定石だ。

// ✅ 推奨:スプレッド構文で新しいオブジェクトを生成する
const UserProfile = ({ user, onUpdate }) => {
const handleNameChange = () => {
// 元のuserを直接いじらず、新しいオブジェクトを生成して渡す
const newUser = { …user, name: “新しい名前” };
onUpdate(newUser);
};

return ;
};

3. さらに一歩先へ:複雑な階層の更新

ネストが深いオブジェクトや配列の更新は、スプレッド構文だけだとコードが汚くなりがちだ。そんなときは、以下の3つのアプローチを状況に応じて使い分けてほしい。

1. 小さなデータなら: スプレッド構文をネストさせる(2〜3階層までなら許容範囲)。
2. ネストが深いなら: `Immer` というライブラリを検討する。これは「ミュータブルな書き方をしているように見えて、裏側ではイミュータブルに変換してくれる」という神ツールだ。
3. 状態管理が肥大化したら: `useReducer` を活用する。アクションを定義することで、データの更新ロジックをコンポーネントの外へ切り出せる。

サンプル:`useReducer` を使った少し複雑な更新例

import { useReducer } from ‘react’;

// 更新ロジックを純粋関数として外出しする
const userReducer = (state, action) => {
switch (action.type) {
case ‘UPDATE_NAME’:
return { …state, name: action.payload };
default:
return state;
}
};

const UserComponent = () => {
const [user, dispatch] = useReducer(userReducer, { name: ‘山田’, age: 25 });

return (

{user.name}

{/ 状態更新はdispatchを介して行う /}

);
};

最後に:プロのエンジニアとしての矜持

Reactにおける不変性は、単なる「お作法」じゃない。それは、「予測可能で、バグが入り込みにくく、スケールするアプリケーションを作る」ための設計思想そのものだ。

Propsを直接いじりたくなったら、一度立ち止まって考えてみてほしい。「これはReactの流儀に反していないか?」と。その一瞬の迷いが、将来の自分やチームメンバーを数時間のデバッグ地獄から救うことになるはずだ。

次は、この「不変性」をさらに突き詰めた「React.memo」との組み合わせについて話そうか。また現場で会おう。

コメント

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