출처: React doc
in React, you treat state as immutable!
So far you’ve been working with numbers, strings, and booleans. These kinds of JavaScript values are “immutable”, meaning unchangeable or “read-only”. You can trigger a re-render to replace a value:
Now consider an object in state:
const [position, setPosition] = useState({ x: 0, y: 0 });
Technically, it is possible to change the contents of the object itself. This is called a mutation:
position.x = 5;
However, although objects in React state are technically mutable, you should treat them as if they were immutable—like numbers, booleans, and strings. Instead of mutating them, you should always replace them.
In other words, you should treat any JavaScript object that you put into state as read-only.
But without using the state setting function, React has no idea that object has changed.
👉 이론적으로 object는 mutable할 수 있지만, immutabl을 지켜줘야 한다. 즉, object를 update할 시 불변성을 지켜줘야한다. 우리가 아래의 코드와 같이 let을 안쓰고 const를 쓰는 이유는 react의 불변성을 나타내기 위함이다.
const [name, setName] = useState('jun')
... spread syntax is “shallow”—it only copies things one level deep. This makes it fast, but it also means that if you want to update a nested property, you’ll have to use it more than once.
👉 spread syntax는 one level만 복사한다. two level로 들어가는 순간 얕은 복사가 이루어진다.
[
student { // one level
age: 20, // two level
weight: 50,
]
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
위와 같이 중첩된 object일 경우 아래와 같이 state 값을 변경할 수 있다.
setPerson({
...person, // Copy other fields
artwork: { // but replace the artwork
...person.artwork, // with the same one
city: 'New Delhi' // but in New Delhi!
}
});
아래와 같이 작성해도 얕은 복사로 인해 state가 업데이트가 되지만 불변성을 지켜주기 위해 우리는 위와 같이 state를 업데이트 해줘야 한다.
person.artwork.city = 'New Delhi';
setPerson({...person}});
※ 얕은 복사를 해도 state값이 변경되는 이유는?
https://velog.io/@tree0787/shallow-and-deep-copy
immer
If your state is deeply nested, you might want to consider flattening it. But, if you don’t want to change your state structure, you might prefer a shortcut to nested spreads.
To try Immer:
Run npm install use-immer to add Immer as a dependency
Then replace import { useState } from 'react' with import { useImmer } from 'use-immer'
Here is the above example converted to Immer:
import { useImmer } from 'use-immer';
export default function Form() {
const [person, updatePerson] = useImmer({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
updatePerson(draft => {
draft.name = e.target.value;
});
}
function handleTitleChange(e) {
updatePerson(draft => {
draft.artwork.title = e.target.value;
});
}
function handleCityChange(e) {
updatePerson(draft => {
draft.artwork.city = e.target.value;
});
}
function handleImageChange(e) {
updatePerson(draft => {
draft.artwork.image = e.target.value;
});
}
return (
<>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<label>
Title:
<input
value={person.artwork.title}
onChange={handleTitleChange}
/>
</label>
<label>
City:
<input
value={person.artwork.city}
onChange={handleCityChange}
/>
</label>
<label>
Image:
<input
value={person.artwork.image}
onChange={handleImageChange}
/>
</label>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img
src={person.artwork.image}
alt={person.artwork.title}
/>
</>
);
}
👉 spread연산자가 쓰기 번거러울 때는 immer