こんにちは!Reactの学習、楽しく進めていますか?
画面がスイスイ動く魔法のようなアプリが作れる一方で、最初につまづきやすいのが「あれ?画面がうまく更新されないぞ……?」という謎の現象です。
その原因の多くは、今回一緒に紐解いていく「オブジェクト状態の不変性(Immutability:イムミュータビリティ)」というルールに隠されています。
なんだか難しそうな名前が聞こえてきましたが、大丈夫です!身近な例えを交えながら、優しくゆっくり解説していきますので、ぜひコーヒーでも飲みながらリラックスして読んでいってくださいね。
—
1. なぜ「直接書き換えちゃいけない」の?
Reactの世界に入る前に、まずは普段のお買い物に例えて考えてみましょう。
あなたは今、お友達とシェアする「お菓子の詰め合わせボックス」のメモを作っています。このメモが、Reactでいう「状態(State)」です。
const snackBox = { cookies: 3, chocolates: 2 };
ここで、チョコレートを1つ食べたので、メモを直接こう書き換えたとします。
snackBox.chocolates = 1; // 直接書き換えちゃった!
人間であれば「ああ、チョコが1つ減ったんだな」と分かりますよね。
しかし、Reactくんは超ド天然の几帳面な秘書さんだと思ってください。
秘書(React)は、こう思っています。
> 「あれ? `snackBox` という『箱の住所(メモリ上の参照)』は、さっきと全く同じだぞ。箱自体は新しくなっていないから、中身を確認しなくてもいいや!よし、画面の再描画(レンダリング)はサボっちゃお!」
結果として、コードの中ではデータが変わっているのに、画面の表示が古いまま変わらないという、初心者泣かせのバグが完成してしまいます。これが、直接オブジェクトを書き換える(ミューテーションする)ことの罠なんです。
—
2. 不変性(Immutability)ってなに?
じゃあどうすればいいのか? ここで登場するのが「不変性(Immutability)」という考え方です。
一言でいうと、「古いデータを直接いじらず、中身をコピーして『全く新しい別の箱』をまるごと用意しなさい」というルールです。
先ほどのお菓子の例なら、こうします。
1. 今あるお菓子の箱から、中身をそっくりそのまま新しい箱に引っ越しさせる。
2. 新しい箱の中のチョコレートの数だけ「1」に変更する。
3. 「はい、新しい箱を用意しましたよ!」とReactくんに渡す。
これを見たReactくんは、「おっ!さっきとは別の新しい箱が届いたぞ!じゃあ、最新の情報に合わせて画面をピカピカに塗り替えなきゃ!」と、大慌てで画面を更新してくれます。
—
3. コードで見てみよう!NG例とOK例
それでは、実際のReactのコードで違いを見てみましょう。
今回は、「ユーザーのプロフィール(名前と年齢)」を管理するコンポーネントを考えてみます。
🚨 やっちゃいけない例(直接書き換え=ミューテーション)
まずは、やってしまいがちな「NGパターン」です。
import React, { useState } from ‘react’;
export default function BadProfile() {
const [user, setUser] = useState({ name: ‘田中くん’, age: 20 });
const updateAge = () => {
// ❌ 【NG】既存のオブジェクトを直接書き換えている(ミューテーション)
user.age = 21;
// そのままReactに「変わったよ!」と渡す
setUser(user);
};
return (
名前: {user.name}
年齢: {user.age}才
{/ ボタンを押しても、画面の年齢が21才に変わらない(または再描画されない)ことがあります /}
);
}
この書き方だと、`user`という箱の住所が変わっていないため、Reactが変更に気づいてくれません。
—
✨ 正しい書き方(スプレッド構文で新しい箱を作る)
それでは、お待たせしました!不変性を守った「OKパターン」です。ここでJavaScriptの強力な武器であるスプレッド構文(`…`)が登場します。
import React, { useState } from ‘react’;
export default function GoodProfile() {
const [user, setUser] = useState({ name: ‘田中くん’, age: 20 });
const updateAge = () => {
// ✨ 【OK】スプレッド構文を使って、古いオブジェクトをコピーした「新しいオブジェクト」を作る
setUser({
…user, // 「今までの名前や年齢をここにパパッと展開してコピーしてね」という意味
age: user.age + 1 // 上書きしたい部分だけ新しく書き換える
});
};
return (
名前: {user.name}
年齢: {user.age}才
{/ ボタンを押すと、バッチリ画面が「21才」に更新されます! /}
);
}
このコードのポイントは、`setUser({ …user, age: user.age + 1 })` の部分です。
スプレッド構文(`…user`)を使うことで、元のデータはそのまま大切に残しつつ、メモリ上の「全く新しい箱」を生成してReactに渡すことができます。これぞReactが最も好むスマートなデータの更新方法です。
—
4. 配列(Array)のときも考え方は同じ!
オブジェクトだけでなく、リストやメニューなどの「配列」を状態として持っているときも全く同じルールが適用されます。
例えば、お気に入りのフルーツリストに新しい果物を追加したいときは、`push`メソッド(直接配列を書き換えるメソッド)を使ってはいけません。
// ❌ NGな書き方
const [fruits, setFruits] = useState([‘りんご’, ‘みかん’]);
const addFruit = () => {
fruits.push(‘バナナ’); // 直接追加しちゃダメ!
setFruits(fruits);
};
// ✨ OKな書き方(スプレッド構文やconcatを使う)
const addFruit = () => {
// 古い配列を展開して、最後に新しい要素を追加した「新しい配列」を作る
setFruits([…fruits, ‘バナナ’]);
};
配列の削除や更新(`filter`や`map`など)を行う際も、「元の配列を汚さない(ミューテーションしない)」メソッドを選ぶのがReact開発の鉄則です。
—
さいごに
最初は「わざわざコピーを作るなんて面倒だな……」と感じるかもしれません。実際、筆者も昔はよく直接書き換えて「あれ、画面が動かんぞ?」と頭を抱えていました(笑)。
でも、この「不変性(Immutability)」を意識できるようになると、Reactの挙動が手に取るように分かるようになり、バグの少ない堅牢なアプリケーションが作れるようになります。
もし画面がうまく更新されなくて悩んだら、
「私、今、古い箱を直接いじってないかな? 新しい箱をちゃんと用意してあげたかな?」
と、そっと自分に問いかけてみてくださいね。
あなたのReactの旅を、心から応援しています!今日も一緒に楽しくコードを書いていきましょう!

コメント