本文へスキップ
オブジェクトと参照

Part 0 · React の前に

2 / 64

オブジェクトと参照

「見た目が同じでも別のもの」が、この先ずっと効いてくる

React で扱うデータは、ほとんどがオブジェクトです。 props もオブジェクト、state に入れるデータもたいていオブジェクト、 画面に並べるリストもオブジェクトの配列です。

そしてオブジェクトには、数値や文字列とは違う性質があります。 この性質を知らないままだと、この先ずっと「なぜか動かない」に出会い続けます

オブジェクトは、名前の付いた値の集まり

関連する値をひとまとめにしたものがオブジェクトです。{ } の中に、名前と値の組を並べて作ります。

const user = {
  name: "さとう",
  age: 20,
  admin: false,
};

この nameageプロパティと呼びます。 取り出すときは . でつなぎます。

user.name  // "さとう"
user.age   // 20

入れ子にもできる

const member = {
  name: "すずき",
  address: { city: "東京" },
};

member.address.city  // "東京"

そして、配列に入れる

React で画面に一覧を出すとき、元になるデータはほぼこの形です。

const members = [
  { id: 1, name: "さとう" },
  { id: 2, name: "すずき" },
];

オブジェクトの配列です。 配列の map の章で「実際のデータはたいていこの形」と 出てくるのは、これのことです。

object-basics.ts

// オブジェクト。名前の付いた値をまとめたもの
export const user = {
  name: "さとう",
  age: 20,
  admin: false,
};

// 中の値を取り出す
export const name = user.name; // "さとう"

// 名前を変数で指定したいときだけ、こちらの書き方を使う
const key = "age";
export const age = user[key]; // 20

// 入れ子にもできる
export const member = {
  name: "すずき",
  address: {
    city: "東京",
  },
};

export const city = member.address.city; // "東京"

// オブジェクトの配列。React で扱うデータは、たいていこの形
export const members = [
  { id: 1, name: "さとう" },
  { id: 2, name: "すずき" },
];

見た目が同じでも、同じとはかぎらない

ここからが本題です。2 つの値が同じかどうかを調べるとき、 JavaScript は === を使います。

いろいろな組み合わせを比べる

どれが true で、どれが false になると思いますか

  • 10 === 10?数値は値そのものを比べる
  • "さとう" === "さとう"?文字列も値そのもの
  • { name: "さとう" } === { name: "さとう" }?見た目が同じでも、別々に作った箱
  • [1, 2] === [1, 2]?配列も同じ

数値と文字列は、予想どおりだと思います。 問題は下の 2 つです。まったく同じ見た目なのに false になりました。

数値や文字列は「値そのもの」を比べます。 ところがオブジェクトと配列は違います。 比べているのは中身ではなく、同じ箱を指しているかどうかです。

代入で写されるのは「どの箱か」originalcopy{ name: "さとう" }=== true箱は 1 つしかない。どちらから書き換えても、同じ箱が変わる別々に作れば、中身が同じでも別の箱box1box2{ name: "さとう" }{ name: "さとう" }=== false
=== が見ているのは中身ではなく、同じ箱を指しているかどうか

{ name: "さとう" } と書くたびに、新しい箱が 1 つ作られます。 中身が同じでも、別々に作った以上は別の箱です。 だから === は false を返します。

reference.ts

// 数値や文字列は「値そのもの」が入る
export const a = 10;
export const b = a; // 10 という値が写される

// 中身が同じでも、別々に作ったオブジェクトは別のもの
export const box1 = { name: "さとう" };
export const box2 = { name: "さとう" };

export const sameLooking = box1 === box2; // false

// 代入したときに写されるのは「どの箱か」であって、箱の中身ではない
export const box3 = box1;

export const sameBox = box1 === box3; // true

// box3 経由で書き換えると、box1 も変わって見える。同じ箱だから
box3.name = "すずき";

export const nameFromBox1 = box1.name; // "すずき"

// 写すのではなく、新しい箱を作れば影響しない
export const box4 = { name: box1.name };
box4.name = "たかはし";

export const box1StillSame = box1.name; // "すずき" のまま

代入で写されるのは「どの箱か」

では、オブジェクトを別の名前に入れるとどうなるでしょうか。

const original = { name: "さとう" };
const copy = original;

copy.name = "すずき";

copy の名前を書き換えました。original はどうなっていると思いますか。

写してから、片方を書き換える

2 つのボタンを押し比べる

どちらの名前も変わった?
ボタンを押してください

両方とも「すずき」になります。copy = original で写されたのは中身ではなく、どの箱を指すかだけだからです。 名札が 2 枚になっただけで、箱は 1 つしかありません。

右のボタン(新しい箱を作って写す)を押すと、結果が変わります。{ name: original.name } と書いた時点で別の箱が作られているので、 書き換えても元には影響しません。

reference.ts

// 数値や文字列は「値そのもの」が入る
export const a = 10;
export const b = a; // 10 という値が写される

// 中身が同じでも、別々に作ったオブジェクトは別のもの
export const box1 = { name: "さとう" };
export const box2 = { name: "さとう" };

export const sameLooking = box1 === box2; // false

// 代入したときに写されるのは「どの箱か」であって、箱の中身ではない
export const box3 = box1;

export const sameBox = box1 === box3; // true

// box3 経由で書き換えると、box1 も変わって見える。同じ箱だから
box3.name = "すずき";

export const nameFromBox1 = box1.name; // "すずき"

// 写すのではなく、新しい箱を作れば影響しない
export const box4 = { name: box1.name };
box4.name = "たかはし";

export const box1StillSame = box1.name; // "すずき" のまま

なぜ React でこれが効いてくるのか

React は、何かが変わったかどうかを判断するときにこの === と同じ判定を使います。 中身を 1 つずつ見比べたりはしません。同じ箱かどうかだけを見ます。

この判定が、これから先ずっと顔を出します。

  • state を書き換えたのに画面が変わらない 同じ箱のままだから、React は「変わっていない」と判断する
  • 毎回同じはずの値で、処理が動き続ける 書くたびに新しい箱ができているから、React は「変わった」と判断する

ぴんと来ないと思うので、形だけ見ておきます。

// 1 つめ … 配列に足したのに、画面が変わらない
items.push("みかん");
setItems(items);        // 同じ箱を渡している

// 2 つめ … 中身は毎回同じなのに、処理が止まらない
const options = { unit: "回" };   // 書くたびに新しい箱

どちらも Part 4 以降で、実際に動かしながらやります。いまは「同じ箱かどうかで決まる」だけ持っていってください。

どちらも同じ 1 つの性質から出ています。 症状は正反対ですが、原因は同じです。

reference.ts

// 数値や文字列は「値そのもの」が入る
export const a = 10;
export const b = a; // 10 という値が写される

// 中身が同じでも、別々に作ったオブジェクトは別のもの
export const box1 = { name: "さとう" };
export const box2 = { name: "さとう" };

export const sameLooking = box1 === box2; // false

// 代入したときに写されるのは「どの箱か」であって、箱の中身ではない
export const box3 = box1;

export const sameBox = box1 === box3; // true

// box3 経由で書き換えると、box1 も変わって見える。同じ箱だから
box3.name = "すずき";

export const nameFromBox1 = box1.name; // "すずき"

// 写すのではなく、新しい箱を作れば影響しない
export const box4 = { name: box1.name };
box4.name = "たかはし";

export const box1StillSame = box1.name; // "すずき" のまま

理解できたか確かめる

確認クイズ

const a = { x: 1 }; const b = { x: 1 }; のとき a === b は?

確認クイズ

const a = { x: 1 }; const b = a; b.x = 2; のとき a.x は?

確認クイズ

React が「値が変わった」と判断するのは、どんなとき?

reference.ts

// 数値や文字列は「値そのもの」が入る
export const a = 10;
export const b = a; // 10 という値が写される

// 中身が同じでも、別々に作ったオブジェクトは別のもの
export const box1 = { name: "さとう" };
export const box2 = { name: "さとう" };

export const sameLooking = box1 === box2; // false

// 代入したときに写されるのは「どの箱か」であって、箱の中身ではない
export const box3 = box1;

export const sameBox = box1 === box3; // true

// box3 経由で書き換えると、box1 も変わって見える。同じ箱だから
box3.name = "すずき";

export const nameFromBox1 = box1.name; // "すずき"

// 写すのではなく、新しい箱を作れば影響しない
export const box4 = { name: box1.name };
box4.name = "たかはし";

export const box1StillSame = box1.name; // "すずき" のまま

この章のまとめ

  • オブジェクトは名前と値の組の集まり。. で取り出す
  • React で扱うデータは、たいていオブジェクトの配列
  • オブジェクトと配列は、中身ではなく「同じ箱か」で比べられる
  • { } と書くたびに新しい箱ができる
  • b = aコピーではない。 名札が 2 枚になるだけ

reference.ts

// 数値や文字列は「値そのもの」が入る
export const a = 10;
export const b = a; // 10 という値が写される

// 中身が同じでも、別々に作ったオブジェクトは別のもの
export const box1 = { name: "さとう" };
export const box2 = { name: "さとう" };

export const sameLooking = box1 === box2; // false

// 代入したときに写されるのは「どの箱か」であって、箱の中身ではない
export const box3 = box1;

export const sameBox = box1 === box3; // true

// box3 経由で書き換えると、box1 も変わって見える。同じ箱だから
box3.name = "すずき";

export const nameFromBox1 = box1.name; // "すずき"

// 写すのではなく、新しい箱を作れば影響しない
export const box4 = { name: box1.name };
box4.name = "たかはし";

export const box1StillSame = box1.name; // "すずき" のまま