🐡

【React】ミュータブル、イミュータブルをざっくり理解

2023/09/04に公開

はじめに

Reactの学習を進めていくなかで、ミュータブル、イミュータブルといった
聞き慣れない用語がでてきたので調べました。そのまとめです。

経緯

Reactの公式ドキュメントにある三目並べを作成中、
Array.slice()で変数の状態を新しい変数にコピー本して
格納していることに違和感を覚えました。
(なぜ、わざわざ新しくデータをつくっているのだろうか、、、)

動的かつ弱い型付け言語

そもそものおさらいですが、JavaScriptは動的型付けの言語です。
変数が直接的に特定のデータ型に関連付けられるではなく、
どの変数にもあらゆる型の値を代入するこができます。

例)変数が代入される値によって型が変わる
let foo = 1; // Number型
foo = "test"; // String型
foo = true; // Boolean型

JavaScriptは暗黙の型変換を可能にしており、型違いによるエラーは発生しません。

データ型

データ型には、ミュータルブル(可変)とイミュータブル(不変)という分け方があり、
それぞれプリミティブ型とオブジェクト型の型があります。

1.オブジェクト型 ミュータブル(可変)

この後に説明する、プリミティブ型に該当しないもの。
オブジェクトや配列などを指す。

2.プリミティブ型 イミュータブル(不変)

typeofの返り値
論理型 boolean
数値型 number
長整数型 bigint
文字列型 string
シンボル型 symbol

実行結果の違い

上記のデータ型をなんとなく抑えた上で、次の例をご覧ください。
ここでは、文字列をすべて大文字に変換する関数を作成し、その中に変数(greeting)を引数にして実行しています。
関数の返り値はHELLOという文字列に変わっていますし、
変数(greeting)に代入した値は小文字のままなので、特に問題がないと思います。

immutable.js
// 引数の値が プリミティブ型 イミュータブル(不変)の場合
const greeting = "hello";
const handleUpperCase = (text) => text.toUpperCase();

console.log(handleUpperCase(greeting)); //HELLO
console.log(greeting); //hello

しかし、次の例ではどうでしょう。

mutable.js
// 引数の値が オブジェクト型 ミュータブル(可変)の場合
const fruits = ['apple', 'banana', 'orange'];
const handleAddItem = (array) => {
  array.push("blueberry");
  return array;
};

console.log(handleAddItem(fruits)); //['apple', 'banana', 'orange', 'blueberry'];
console.log(fruits); //['apple', 'banana', 'orange', 'blueberry'];

これを見てわかるように、配列(fruits)にどこでも再代入はしていませんが、
関数を実行した後からは配列の値が変更されています。問題はこれです。

なぜこれが起きるのでしょうか。

関数に引数として変数を渡すときは、その変数のコピー本を渡します。
ここで、その変数がプリミティブ型なら値を新しいメモリに保存して渡します。
そのため、コピー本を変更してもなにも影響がありません。

しかし、変数の中身がオブジェクト型ならその変数のメモリアドレスを渡します。
そのため、原本とコピー本が同じメモリアドレスを共有しており、コピー本を変更したら原本にも影響がでるのです。

このことから、Reactでステートの変更をするということは、
メモリに保存されている値を変更する行為とゆっても良いと思います。

まとめ

Reactがimmutabilityを大事にする理由は以下2点だと考えます。

  • 予想しないステートの更新を防げる
    元のステートを(一番最初にメモリへ保存されてたデータ)を残しておくことで、
    定義された変数の状態を把握しやすくする。

  • ステートの変更を追跡できる
    オブジェクトを更新させるとき「更新されたオブジェクト」を新しく生成すると、更新前のオブジェクトと更新後のオブジェクトで比較します。これを利用してオブジェクトが更新されたか否かがわかります。

Discussion