こんにちは!フロントエンドの現場を渡り歩いてきたチーフアーキテクトの私です。
Reactを触り始めてすぐの頃、「あれ? 画面の値が変わらない…」「前に入れたデータが消えちゃう…!」という謎の怪奇現象に頭を抱えたことはありませんか? 大丈夫です、それ、100人中99人が最初に通る「Reactの洗礼」ですから安心してくださいね。
今日は、そんな初心者のあなたが思わず「なるほど!」と膝を打つ、Reactの状態管理における超重要ルール「不変性(イミュニティ)」について、身近な例えを交えながら優しく紐解いていきたいと思います。
—
1. なぜReactはデータが変わっても気づいてくれないのか?
まずは、Reactの気持ちになって考えてみましょう。
Reactは非常に几帳面な管理人さんだと思ってください。この管理人さん、お部屋(画面)の模様替えがあったかどうかを確認するときに、こんな風にチェックしています。
- 「お部屋の『中身の家具』が変わったかどうか」ではなく、
- 「お部屋の『住所(オブジェクトや配列の参照場所)』そのものが新しいものに替わったかどうか」
つまり、すでにある家具の位置をコソコソと動かしても(これを「ミューテーション=直接書き換え」と言います)、住所が変わっていなければ、管理人さんは「おや、何も変わっていないな」とスルーしてしまうんです。これが、画面が再描画されずにお手上げ状態になる原因です。
身近な例え:お買い物のレシートメモ
例えば、あなたが「今日の買い物リスト」というメモ用紙(オブジェクト)を持っているとします。
- ダメなやり方(直接書き換え): 同じメモ用紙の「りんご」を消して、上からグシャグシャと「みかん」と書き直す。これだと、メモ用紙自体(住所)は古いままなので、周りの人は変化に気づきません。
- 正しいやり方(不変性を守る): 新しい綺麗なメモ用紙を一枚用意して、「りんご」ではなく最初から「みかん」と書いた新しいリストを新しく作り直す。これなら「あ、新しいリストになったんだな!」と誰の目にも一目瞭然ですよね。
これが、Reactでいう「不変性を保った状態更新」の本質です。データを直接いじるのではなく、「新しいデータを作ってごっそり置き換える」のが鉄則なんです。
—
2. スプレッド構文(`…`)でお部屋を新しくする作戦
JavaScriptには、この「新しいデータをごっそり作る」をめちゃくちゃエレガントに実現してくれる魔法の構文があります。それがスプレッド構文(3点リーダー:`…`)です。
百聞は一見にしかず、実際のコードを見てみましょう。ユーザーのプロフィール情報(名前と年齢)を管理するコンポーネントを例にします。
import React, { useState } from ‘react’;
function UserProfile() {
// 状態として「名前」と「年齢」を持つオブジェクトを定義します
const [user, setUser] = useState({ name: ‘たろう’, age: 20 });
const handleBirthday = () => {
// 【やってはいけないこと(直接書き換え)】
// user.age = 21;
// setUser(user); // これだとReactは「あれ?中身変わってないよ?」と勘違いします
// 【正しいやり方(不変性を守る更新)】
setUser({
…user, // ① 今までの「たろう・20歳」の情報を一旦すべて展開してコピー!
age: 21 // ② 年齢のところだけ「21」に上書きする!
});
};
return (
お名前:{user.name}
年齢:{user.age}歳
{/ ボタンを押すと年齢が1つ増えます /}
);
}
export default UserProfile;
このコードのポイントは、`{ …user, age: 21 }` という部分です。
「今の `user` の中身をぜんぶバラして新しい箱に詰め替えて、その上で `age` だけ新しくする」という手順を踏んでいます。これによって「新しいオブジェクト(住所)」が生成されるため、Reactの管理人さんも「おっ、データが新しくなったぞ!画面を再描画しなきゃ!」とすぐに気づいてくれるわけです。
—
3. 配列のときだって怖くない!「追加・削除」の基本テクニック
オブジェクトができるようになったら、次は配列(リスト)です。お買い物リストに新しい商品を追加するシチュエーションを考えてみましょう。
配列のときも、絶対に `push` メソッドを使ってはいけません(`push` は元の配列を直接汚してしまうため)。代わりに、スプレッド構文や `filter` メソッドを使います。
import React, { useState } from ‘react’;
function ShoppingList() {
// お買い物リストの初期状態(配列)
const [items, setItems] = useState([‘りんご’, ‘バナナ’]);
const [inputText, setInputText] = useState(”);
// 1. 新しいアイテムを追加する関数
const addItem = () => {
if (inputText.trim() === ”) return;
// スプレッド構文を使って、「今までのリストの要素」+「新しい要素」を持った新しい配列を作る!
setItems([…items, inputText]);
// 入力欄を空に戻す
setInputText(”);
};
// 2. アイテムを削除する関数(おまけ)
const removeItem = (targetIndex) => {
// filterメソッドを使って、「クリックされた番号以外のもの」を集めた新しい配列を作る!
const newItems = items.filter((_, index) => index !== targetIndex);
setItems(newItems);
};
return (
私のお買い物リスト
setInputText(e.target.value)}
placeholder=”買うものを入力…”
/>
-
{items.map((item, index) => (
-
{item}
))}
);
}
export default ShoppingList;
配列の追加では `[…items, inputText]` と書くことで、「既存の要素の列の末尾に、新しい要素をポンと置いた『新しい配列』」を作っています。削除のときも元の配列をいじらず、`filter` で条件に合うものだけを抽出して新しい配列を作っていますね。この「新しく作る」という感覚、少しずつ掴めてきたのではないでしょうか?
—
4. さらに複雑なデータ構造になったら?(実務で使えるアドバイス)
実務の現場では、もっと深い階層を持ったオブジェクト(例えば、ユーザーの中に住所オブジェクトがあって、その中にさらに細かい情報があって…というネストした構造)に出会うことがよくあります。
そんなとき、スプレッド構文を何重にも書いていると、コードがパズルゲームのように難解になってしまいます。
「あぁ、もう頭がパンクしそう!」となったときは、現場のプロも愛用している `Immer(イマー)` というライブラリの導入を検討してみてください。
Immerを使うと、こんな風に書くことができます:
// 概念的なイメージ(Immer使用時)
// まるで直感的にデータを書き換えているかのように書けるのに、内部では自動的に安全な不変データを作ってくれる魔法のツールです
updateUser(draft => {
draft.address.zipCode = ‘123-4567’; // 普通に代入しているように書ける!
});
初学者のうちはまず素の JavaScript(スプレッド構文)で「新しいデータを作り直す」という感覚を体に染み込ませるのが一番の近道ですが、「こういう助け舟もあるんだな」と心の片隅に置いておいてもらえると、いざという時に救われますよ。
—
さいごに
不変性を保った状態更新は、最初は少し回りくどく感じるかもしれません。「なんで直接書き換えちゃいけないの?」とモヤモヤすることもあるでしょう。
ですが、これはReactという素晴らしいパートナーと仲良く仕事をしていくための「お約束」のようなものです。このルールを守ってあげさえすれば、Reactはあなたの最高の味方になって、いつでもキビキビと綺麗な画面を描き出してくれます。
もしコードを書いていて「あれ、画面が動かないな」と思ったら、今日の記事を思い出して「あ、新しいデータとして作り直せてるかな?」とそっと確認してみてくださいね。
あなたのReact開発ライフが、楽しく実りあるものになりますように。現場のチーフアーキテクトより、応援しています!

コメント