はじめに
React
では、イミュータブル
な操作を必要とされることが多いですが、そもそもミュータブル・イミュータブル
についていまいち理解していなかったのでまとめます。
Javascriptのデータ型
Javascriptのデータ型はprimitive
とobject
があります。
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"}
上記の例のようにオブジェクトのコピーをした状態を図に表すと、以下のようになります。
objectOne
とobjectTwo
は同じオブジェクトを参照している状態であるため、ミュータブル
な操作になります。
新しいオブジェクトを作成してコピーする
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.
参考にしたサイト