LoginSignup
2
2

More than 1 year has passed since last update.

はじめに

Reactでは、イミュータブルな操作を必要とされることが多いですが、そもそもミュータブル・イミュータブルについていまいち理解していなかったのでまとめます。

Javascriptのデータ型

Javascriptのデータ型はprimitiveobjectがあります。

primitiveはイミュータブルで、objectはミュータブルです。
ミュータブル変更可能を意味し、イミュータブルは変更不可能を意味します。

primitiveの場合はイミュータブルであるため、下記のようにコピーをしてその値を変更してもコピー元に対して影響を与えることはありません。

    let a = "hello"
    let b = a
    b = "hola"
    console.log(a) // => "hello"
    console.log(b) // => "hola"

反対にobjectの場合はミュータブルであるため、コピー元にまで影響が出てしまいます。

    const objectOne = {
        first_name: "hello",
        last_name: "world"
    }

    const objectTwo = objectOne
    objectTwo.first_name = "hola"
    
    console.log(objectOne) // => {first_name: "hola", last_name: "world"}
    console.log(objectTwo) // => {first_name: "hola", last_name: "world"}

Reactでイミュータブルな操作をするためには、変数もしくは定数primitiveを代入することと、objectを代入することの違いを理解する必要があります。

オブジェクトのコピー

    const objectOne = {
        first_name: "hello",
        last_name: "world"
    }

    const objectTwo = objectOne
    objectTwo.first_name = "hola"
    
    console.log(objectOne) // => {first_name: "hola", last_name: "world"}
    console.log(objectTwo) // => {first_name: "hola", last_name: "world"}

上記の例のようにオブジェクトのコピーをした状態を図に表すと、以下のようになります。

objectOneobjectTwoは同じオブジェクトを参照している状態であるため、ミュータブルな操作になります。

新しいオブジェクトを作成してコピーする

    const objectOne = {
        first_name: "hello",
        last_name: "world"
    }
    
    const objectTwo = {...objectOne}
    objectTwo.first_name = "hola"
    
    console.log(objectOne) // => {first_name: "hello", last_name: "world"}
    console.log(objectTwo) // => {first_name: "hola", last_name: "world"}

上記のようにスプレッドオペレーターを用い、新しいオブジェクトを作成してからコピーした際の状態を図で表すと以下のようになります。この場合は、それぞれが異なるオブジェクトを参照しているため、イミュータブルな操作になります。

objectTwo.first_name = "hola"のように変更した際の状態は下記のようになります。
一方に変更を加えても他方に影響を与えることはありません。

2次元以上でイミュータブルな操作をする

ReactでTodoリストを作るときなどは、以下のような要素がオブジェクトの配列をstateとして持つことが多いと思います。
オブジェクトがネストしている状態なので、ミュータブルな操作をしてしまいバグらせることが多いです。

    [
        {
          "id": "number",
          "content": "string"
        }
    ]
    const todo = [
        {
          id: 1,
          content: "content1",
        },
        {
          id: 2,
          content: "content2",
        },
        {
          id: 3,
          content: "content3",
        }
      ]

具体例として上記のような配列で、最初の要素のcontentを変更する場合を考えます。

    const copyTodo = todo.slice()
    copyTodo[0].content = "content4"
    console.log(todo)
        // => [{id: 1, content: 'content4'}, {id: 2, content: 'content2'}, {id: 3, content: 'content3'}
    console.log(copyTodo)
        // => [{id: 1, content: 'content4'}, {id: 2, content: 'content2'}, {id: 3, content: 'content3'}

sliceを使ってコピーした配列に変更を加えた場合、コピー元にも変更が及んでしまっています。これではミュータブルな操作です。
このときの状態を図に表すと以下のようになります。

イミュータブルな操作にするには下記のような状態にしたいです。

以上を踏まえると、イミュータブルな操作にするには下記のようなコードになります。

    const copyTodo = todo.map((preObject) => {
        return {...preObject}
    })
    copyTodo[0].content = "content4"
    console.log(todo)
        // => [{id: 1, content: 'content1'}, {id: 2, content: 'content2'}, {id: 3, content: 'content3'}
    console.log(copyTodo)
        // => [{id: 1, content: 'content4'}, {id: 2, content: 'content2'}, {id: 3, content: 'content3'}

なぜイミュータブルである必要があるのか

Immutable.js allows us to detect changes in JavaScript objects/arrays without resorting to the inefficiencies of deep equality checks, which in turn allows React to avoid expensive re-render operations when they are not required. This means Immutable.js performance tends to be good in most scenarios.

参考にしたサイト

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2