Part 0 · React の前に
2 / 64 章
オブジェクトと参照
「見た目が同じでも別のもの」が、この先ずっと効いてくる
React で扱うデータは、ほとんどがオブジェクトです。 props もオブジェクト、state に入れるデータもたいていオブジェクト、 画面に並べるリストもオブジェクトの配列です。
そしてオブジェクトには、数値や文字列とは違う性質があります。 この性質を知らないままだと、この先ずっと「なぜか動かない」に出会い続けます。
オブジェクトは、名前の付いた値の集まり
関連する値をひとまとめにしたものがオブジェクトです。{ } の中に、名前と値の組を並べて作ります。
const user = {
name: "さとう",
age: 20,
admin: false,
};この name や age をプロパティと呼びます。 取り出すときは . でつなぎます。
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 になりました。
数値や文字列は「値そのもの」を比べます。 ところがオブジェクトと配列は違います。 比べているのは中身ではなく、同じ箱を指しているかどうかです。
{ 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; // "すずき" のまま