JavaScriptとReactの比較の仕組みを学ぶ:プリミティブ型・参照型とuseState
プリミティブ型と参照型
JavaScriptにおける, プリミティブ型と参照型について解説します.
プリミティブ型
以下にプリミティブ型を示します.
string
number
bigInt
boolean
undefined
symbol
null
プリミティブ型はimmutableな値で、変更することはできません。ただし、変数に新しい値を再割り当てすることは可能です。
let str = "Hello";
str[0] = "P";
console.log(str[0]); // Hと出力される. 値を変更することはできないため, Pとは出力されない
console.log(str); // Hello
str = "Bye"; // 新しい値を割り当てることはできる
console.log(str); // Bye
オブジェクト(参照型)
プリミティブ型以外のものはすべてオブジェクトになります.
- 関数
- オブジェクト
- 配列など
オブジェクトはmutableな値のため, 変更することができます.
// オブジェクト内の関数を変更する
let greet = {
func: () => console.log("Hello"),
};
greet.func = () => {
console.log("Hye");
};
greet.func(); // Hey
// オブジェクトのプロパティを変更する
let obj = {
params: "hoge",
};
obj.params = "change";
console.log(obj); // { params: 'change' }
// 配列の要素を変更する
let arr = ["H", "e", "l", "l", "o"];
arr[0] = "P";
console.log(arr); // [ 'P', 'e', 'l', 'l', 'o' ]
関数や配列がオブジェクトであるか以下のようなコードで確認してみましょう.
function counter() {
counter.count++;
}
counter.count = 0; // プロパティとしてカウンタを持つ
counter();
counter();
console.log(counter.count); // 2
let arr = [1, 2, 3];
arr.count = 3; // プロパティとしてカウントを持つ
console.log(arr.count); // 3
オブジェクトのコピーと比較
次に, オブジェクトのコピーと比較について見ていきましょう.
オブジェクトのコピー
オブジェクトをコピーすると, 参照がコピーされます.
const obj = {
params: "hoge",
};
const copyOfObj = obj;
/* 参照がコピーされる. つまり, obj, copyOfObjは同じ値への参照を保持している(この場合{params:"hoge"}への参照). */
obj.params = "huga"; // paramsの値を変更
console.log(obj); // { params: 'huga' }
console.log(copyOfObj); // { params: 'huga' }
// 同じオブジェクトへの参照を保持していたので, 2つの変数は同じ値を出力する
// プリミティブ型のコピーは値そのものがコピーされる
let a = 1;
let b = a;
a = 2;
console.log(a); // 2
console.log(b); // 1
参照のコピー時のイメージです
オブジェクトの比較
オブジェクトの比較では変数が保持している参照同士の比較が行われます.
この場合, copyOfObj
,obj
は同じ参照を保持しているので, 変数を比較したときに, true
が出力されます.
const obj = {
params: "hoge",
};
const copyOfObj = obj; // 参照をコピー
console.log(obj === copyOfObj) // true
先ほど述べたように, オブジェクトでは参照同士の比較が行われるので, 以下のような中身が同じでも参照が異なる場合, 変数同士を比較すると, false
が出力されます.
const obj = {
params: "hoge",
};
const sameObj = {
params: "hoge",
};
console.log(obj === sameObj); // false
ReactのuseStateのセッター関数について
ReactのuseState
は, 内部で現在のstate
の値と新しいstate
の値を比較する際にObject.is()
メソッドを使用しています. このメソッドは2つの引数を受け取り, 値が異なればfalse
, 同じであればtrue
を返します。===
演算子と似ていますが, いくつか違いがあります.
Object.is()と===の違い
Object.is()
, ===
では符号付きのゼロ
とNaN
の扱いが違います.
===
演算子では符号付きゼロの比較ではtrue
になりますが, Object.is()
ではfalse
になります.
NaN
の比較では, ===
演算子の場合, false
になりますが, Object.is()
ではtrue
になります.
console.log(0 === -0); // true
console.log(Object.is(0, -0)); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
参考:
配列やオブジェクトのstateを更新するときに新しい配列やオブジェクトを設定する理由
最後に, オブジェクトや関数でstate
を更新するときに, 新しい配列やオブジェクト渡す必要があるのかを説明します. 以下のようなコンポーネントを例にします.
const [student, setStudent] = useState({ name: 'Kato', age: 19 })
useEffect(() => console.log('render'), [student])
return (
<div>
<button onClick={() => setStudent(student)}>studentをそのまま, setStudentに渡す</button>
<button onClick={() => setStudent({ ...student })}>
studentをスプレッド構文で展開してから, setStudentに渡す
</button>
</div>
)
1つ目のボタンを教えたときには再レンダリングされません.
この場合, 以下のような比較が行われます.
オブジェクト同士の比較になるので, 参照が比較されることになります. よって, この比較はtrue
になり, 現在の値
と更新したい値
は同じだと判断されて再レンダリングはされません.
Object.is(student(現在の値), student(更新したい値))
2つ目のボタンを押したときには, 再レンダリングされます.
2つ目のボタンが押されたときには以下のような比較が行わます.
この場合も, オブジェクトの比較になるので, 参照が比較されます. しかし, student
と{...student}
では参照が異なります.
よって, 比較結果はfalse
になり再レンダリングされます.
Object.is(student(現在の値), { ...student }(更新したい値))
以下の図のように, オブジェクトや配列は参照を持つため, 参照が同じかどうかで判定が行われます.
メモリの中のイメージ
Discussion