안녕하세요~!
웹파트 YB 한수정입니다.
벌써 4주차네요. 해야 하는 것도 하고 싶은 것도 너무 많아 바쁜 요즘입니당...
이번 글에서는 ‘상호작용성 더하기’에서 다룬 내용 중 State 업데이트 큐, 객체 state 업데이트하기, 배열 state 업데이트하기를 읽어보고, 그 중에서도 객체 state 업데이트에 대해 차근차근 같이 알아보려고 합니다! 완전히 저의 눈높이에서 한줄한줄 찾아보며 정리하는 아티클이라 편하게 보셔도 돼요!
객체 State 업데이트하기
State는 객체를 포함한 모든 종류의 자바스크립트 값을 가질 수 있습니다.
- React의 state는 객체, 배열, 숫자, 문자열, 불리언 등 모든 자바스크립트 값을 저장할 수 있습니다.
- 예를 들어, const [x, setX] = useState(0);에서 x는 숫자이고, const [position, setPosition] = useState({ x: 0, y: 0 });에서는 position이 객체입니다.
하지만 React state가 가진 객체를 직접 변경해서는 안 됩니다.
- React state를 다룰 때 직접 수정하는 방식은 피해야 합니다.
- 예를 들어, position.x = 5;와 같이 객체 안의 값을 바로 수정하면 안 된다는 것입니다.
- 왜? React는 상태가 변할 때 컴포넌트를 리렌더링해야 하는데, 객체를 직접 수정하면 React가 그 변화를 감지하지 못할 수 있습니다.
객체를 업데이트하고 싶을 때는 새로운 객체를 생성하여 (또는 기존 객체의 복사본을 만들어), state가 복사본을 사용하도록 하세요.
- 새로운 객체를 만들어서 상태를 교체해야 한다는 의미입니다.
- 예를 들어, position 객체의 x 값을 변경하려면, position.x = 5;처럼 기존 객체를 직접 수정하지 않고, 새로운 객체를 만들어야 합니다.
- setPosition({ ...position, x: 5 }); 에서 { ...position, x: 5 }는 기존 position 객체를 복사하고 x 값을 5로 덮어쓰는 새로운 객체를 만드는 것입니다.
- 왜? React는 새로운 객체가 생겼다는 사실을 감지하고, 그에 따라 상태가 변경된 것으로 간주하여 리렌더링을 하기 때문입니다.
React state에서 객체를 올바르게 업데이트하는 방법
const [x, setX] = useState(0); // 숫자
const [name, setName] = useState("React"); // 문자열
const [isOpen, setIsOpen] = useState(true); // 불리언
const [position, setPosition] = useState({ x: 0, y: 0 }); // 객체
State에는 모든 종류의 자바스크립트 값을 저장할 수 있습니다. 자바스크립트 값들은 변경할 수 없거나 읽기 전용을 의미하는 불변성을 가집니다. 값을 교체하기 위해서는 리렌더링이 필요합니다.
- 불변성(immutability)이란 원래의 값을 변경하지 않고 새로운 값을 만들어서 교체하는 것을 의미합니다.
- React에서는 상태가 변경되면 컴포넌트를 다시 리렌더링(render) 해야 합니다.
- 리렌더링은 상태가 변할 때, UI를 다시 그려주는 과정입니다.
setX(5);
state인 x가 0에서 5로 바뀌었지만, 숫자 0 자체는 바뀌지 않습니다. 자바스크립트 값인 원시 값은 변경할 수 없습니다.
- setX는 x의 값을 변경하는 업데이트 함수입니다.
- x라는 상태 변수에 새로운 값 5가 할당됩니다. React는 이 변경을 감지하고, 상태 변경에 따른 리렌더링을 수행합니다.
- 원시값은 불변이라 값 자체를 변경할 수 없고 변경하기 위해서는 새로운 값을 할당해야 합니다.
const [position, setPosition] = useState({ x: 0, y: 0 });
기술적으로 객체 자체의 내용은 바꿀 수 있습니다. 이것을 변경(mutation)이라고 합니다.
- position은 객체입니다. 객체는 변경(mutation)이 가능합니다. 즉, 객체 내부의 값을 직접 수정할 수 있다는 의미입니다.
- 하지만 React에서는 객체나 배열을 직접 수정하지 않고, 새로운 객체를 만들어서 상태를 교체하는 방식으로 업데이트해야 합니다.
position.x = 5;
하지만 React state의 객체들이 기술적으로 변경 가능할지라도, 숫자, 불리언, 문자열과 같이 불변성을 가진 것처럼 다루어야 합니다. 객체를 변경하는 대신 교체해야 합니다.
- 위 코드에서 position.x = 5;는 객체의 속성인 x의 값을 직접 변경한 예시입니다.
- 객체나 배열도 불변성을 지켜야 React가 상태 변경을 제대로 감지하고 리렌더링을 수행할 수 있습니다.
setPosition({ ...position, x: 5 });
{ ...position, x: 5 }는 position 객체를 복사한 후, x 값을 새로운 값으로 덮어쓰기는 방식입니다. 이때, 기존 객체를 변경하지 않고 새로운 객체를 만들어서 상태를 교체합니다.
state에 저장한 자바스크립트 객체는 어떤 것이라도 읽기 전용인 것처럼 다루어야 합니다.
아래 코드에서는 커서를 움직일 때 빨간 점이 이동해야 하지만, 초기 위치에 머무르는 모습을 알 수 있습니다.
import { useState } from 'react';
export default function MovingDot() {
const [position, setPosition] = useState({
x: 0,
y: 0
});
return (
<div
onPointerMove={e => {
position.x = e.clientX;
position.y = e.clientY;
}}
style={{
position: 'relative',
width: '100vw',
height: '100vh',
}}>
<div style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}} />
</div>
)
}
위에서 차근차근 알아봤듯이, 아래 부분이 문제임을 알 수 있습니다.
position.x = e.clientX와 position.y = e.clientY는 position 객체를 직접 수정하는 코드입니다. React는 상태가 바뀔 때마다 리렌더링을 해 UI를 업데이트하는데, 객체를 직접 수정하면 React는 상태 변화가 일어난 것을 감지하지 못하고, 리렌더링을 트리거하지 않습니다.
onPointerMove={e => {
position.x = e.clientX;
position.y = e.clientY;
}}
문제를 해결하기 위해서는 상태 객체는 position 객체를 직접 수정하지 말고, 새로운 객체를 만들어서 그것을 setPosition으로 전달해야 합니다. 아래 코드와 같이 새 객체를 생성하여 state 설정함수로 전달해야 리렌더링을 발생시킬 수 있습니다.
이렇게 하면, React는 새로운 객체가 생성된 것을 감지하고, 리렌더링을 실행하여 빨간 점이 커서 위치에 맞게 움직이게 됩니다.
onPointerMove={e => {
setPosition({
x: e.clientX,
y: e.clientY
});
}}
DEEP DIVE
지역변경은 괜찮습니다
- State 객체를 변경하면 문제가 생깁니다.
position.x = e.clientX;
position.y = e.clientY;
position은 React의 state로 관리되고 있기 때문에 직접 수정하는 것은 불변성을 어기는 방식입니다. 또한, 이렇게 객체를 직접 수정하면, React는 상태 변화가 일어났다고 알지 못해 리렌더링이 되지 않습니다.
- 새로운 객체를 만들어서 수정하는 것은 괜찮습니다.
const nextPosition = {};
nextPosition.x = e.clientX;
nextPosition.y = e.clientY;
setPosition(nextPosition);
새로 생성한 객체를 수정하는 것은 괜찮습니다. React는 새로운 객체를 받게 되므로 상태를 정상적으로 교체하고 리렌더링을 트리거합니다.
- 지역 변경(local mutation)의 개념
setPosition({
x: e.clientX,
y: e.clientY
});
새로운 객체를 만들고 그 객체를 수정하는 것은 지역 변경이라고 합니다. 렌더링 중에만 로컬에서 객체를 수정하는 것이므로, UI 업데이트가 필요할 때 편리하게 사용할 수 있습니다.
지역 변경은 새로운 객체를 수정하는 것이므로 상태 변경을 안정하게 할 수 있는 방법입니다.
폼에서 한 개의 필드만 수정하고 나머지 필드는 이전 값을 유지하고 싶을 수 있습니다.
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com'
});
function handleFirstNameChange(e) {
person.firstName = e.target.value;
}
function handleLastNameChange(e) {
person.lastName = e.target.value;
}
function handleEmailChange(e) {
person.email = e.target.value;
}
return (
<>
<label>
First name:
<input
value={person.firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:
<input
value={person.lastName}
onChange={handleLastNameChange}
/>
</label>
<label>
Email:
<input
value={person.email}
onChange={handleEmailChange}
/>
</label>
<p>
{person.firstName}{' '}
{person.lastName}{' '}
({person.email})
</p>
</>
);
}
하지만, 이 코드는 input 필드에 onChange 핸들러가 state를 변경하기 때문에 동작하지 않습니다.
function handleFirstNameChange(e) {
person.firstName = e.target.value; // 직접 수정
}
위 코드에서는 person 객체의 firstName을 직접 수정하고 있습니다. 앞에서 계속 강조했듯이 React의 state는 불변성을 지켜야 하므로 상태를 직접 수정하면 React가 상태변화를 알지 못해 리렌더링이 발생하지 않습니다. 따라서 새로운 객체를 생성해 상태를 업데이트 해야 합니다.
function handleFirstNameChange(e) {
setPerson({
firstName: e.target.value,
lastName: person.lastName,
email: person.email
});
}
위 코드에서는 setPerson을 사용하여 새로운 객체를 만들어 상태를 업데이트 합니다. 하지만, 이렇게 직접 lastName과 email을 복사하려면 매번 모든 필드를 다 나열해야 해 번거로울 수 있습니다.
객체 전개 문법으로 간편하게 복사하기!
function handleFirstNameChange(e) {
setPerson({
...person, // 기존 객체의 모든 필드를 복사
firstName: e.target.value // 바뀐 필드만 덮어쓰기
});
}
전개 문법은 위 코드에서 ...person 부분으로, 전개 문법을 사용하면 기존 객체의 모든 프로퍼를 복사할 수 있습니다. firstName만 덮어쓰고 나머지 lastName과 email은 그대로 유지됩니다.
- 전개 문법의 특징: 얕은 복사(shallow copy)
- 얕은 복사는 객체를 복사할 때, 객체의 첫 번째 레벨만 복사하고 그 내부의 객체나 배열은 복사하지 않고 참조만 합니다.
- 상위 객체는 새로 만들어지지만 내부의 객체는 여전히 원본 객체와 같은 참조를 공유합니다.
const person = {
name: 'John',
contact: { email: 'john@example.com', phone: '123-456-7890' }
};
const personCopy = { ...person }; // 얕은 복사
personCopy.contact.email = 'newemail@example.com';
console.log(person.contact.email); // 'newemail@example.com'
console.log(personCopy.contact.email); // 'newemail@example.com'
- person 객체에는 contact라는 중첩된 객체가 있습니다.
- personCopy는 person 객체를 얕은 복사했기 때문에, person.contact 객체는 복사되지 않고 참조만 복사되었습니다. 즉, personCopy.contact와 person.contact는 같은 객체를 가리킵니다.
- personCopy.contact.email을 수정하면 person.contact.email도 함께 변경됩니다.
그렇다면, 깊은 복사도 있나?
깊은 복사는 객체 내부에 있는 중첩된 객체들도 완전히 복사하므로, personCopy.contact를 수정해도 person.contact에는 영향을 주지 않습니다.
const person = {
name: 'John',
contact: { email: 'john@example.com', phone: '123-456-7890' }
};
// 깊은 복사
const personCopy = JSON.parse(JSON.stringify(person));
personCopy.contact.email = 'newemail@example.com';
console.log(person.contact.email); // 'john@example.com'
console.log(personCopy.contact.email); // 'newemail@example.com'
따라서, personCopy의 contact.email을 수정해도 person.contact.email은 바뀌지 않습니다. 깊은 복사를 했기 때문에 person과 personCopy는 서로 다른 객체를 가지고 있기 때문입니다.
DEEP DIVE
여러 필드에 단일 이벤트 핸들러 사용하기
즉, 각 필드에 대해 별도의 이벤트 핸들러를 작성하는 대신, 하나의 handleChange 함수로 모든 필드를 처리하는 방법을 보여주는 부분입니다.
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com'
});
function handleChange(e) {
setPerson({
...person,
[e.target.name]: e.target.value
});
}
return (
<>
<label>
First name:
<input
name="firstName"
value={person.firstName}
onChange={handleChange}
/>
</label>
<label>
Last name:
<input
name="lastName"
value={person.lastName}
onChange={handleChange}
/>
</label>
<label>
Email:
<input
name="email"
value={person.email}
onChange={handleChange}
/>
</label>
<p>
{person.firstName}{' '}
{person.lastName}{' '}
({person.email})
</p>
</>
);
}
- <input> 태그의 name 속성은 해당 입력 필드의 이름을 지정합니다.
- 이벤트 핸들러인 handleChange는 e.target.name을 사용하여, 어떤 필드가 변경되었는지를 동적으로 알 수 있습니다.
- 동적으로 필드를 업데이트하려면, 대괄호 [ ]를 사용하여 객체 프로퍼티를 동적으로 설정합니다.
객체 프로퍼티를 동적으로 설정한다..? 잠깐 딴 길로 빠져 이게 어떤 것인지 먼저 알아보겠습니다.
동적 프로퍼티
객체 프로퍼티를 동적으로 설정하는 것은 객체의 키(속성명)를 코드 실행 중에 동적으로 결정하는 방법입니다. 자바스크립트에서 객체의 키는 고정된 문자열일 수도 있지만, 동적으로 결정할 수 있습니다. 이를 위해 대괄호 [ ]를 사용합니다.
- 정적 프로퍼티
const person = {
name: 'Soojeong', // 'name'은 고정된 키
};
- 동적 프로퍼티
const key = 'name';
const person = {
[key]: 'Soojeong', // 'name'이 아니라, 'key' 변수의 값을 프로퍼티 이름으로 사용
};
console.log(person.name); // 'Soojeong'
다시 DEEP DIVE로 돌아오겠습니다.
전체 코드중 일부만 떼어서 보겠습니다.
function handleChange(e) {
setPerson({
...person, // 기존 상태 복사
[e.target.name]: e.target.value // 변경된 필드 업데이트
});
}
- e.target.name: 현재 이벤트가 발생한 <input> 요소의 name 속성 값입니다. 예를 들어 firstName, lastName, email 등이 될 수 있습니다.
- e.target.value: 해당 입력 필드의 새로운 값을 가져옵니다.
- [e.target.name]: 이 구문은 동적으로 객체의 프로퍼티를 설정하는 방법입니다. 예를 들어, firstName이 변경되면 person.firstName이 업데이트되게 됩니다.
동적 프로퍼티 업데이트를 통해 필드가 많아져도 하나의 핸들러로 모든 필드를 관리할 수 있습니다. 또한, 이렇게 하면 코드 중복을 줄일 수 있고 각 필드에 대해 별도의 onChange 핸들러를 만들 필요가 없습니다.
얕은 복사에서 한 번 만났던 중첩된 객체에 대해 자세히 알아보겠습니다.
중첩된 객체 구조를 생각한다면, 아래와 같습니다.
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
이 코드에서 person.artwork.city를 업데이트하고 싶다면, 상태를 직접 변경하면 안 되기 때문에 새로운 객체를 생성하여 상태를 업데이트해야 합니다.
❌
person.artwork.city = 'New Delhi';
⭕(객체 복사 후 수정)
const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);
city를 바꾸기 위해서는 ⭕의 코드와 같이 먼저 이전 객체의 데이터로 생성된 새로운 artwork 객체를 생성한 뒤, 그것을 가리키는 새로운 person 객체를 만들어야 합니다. 아래는 객체 전개 문법을 사용한 다른 방법입니다.
⭕(객체 전개 문법을 사용해 수정)
setPerson({
...person, // 다른 필드 복사
artwork: { // artwork 교체
...person.artwork, // 동일한 값 사용
city: 'New Delhi' // 하지만 New Delhi!
}
});
객체 전개 문법(...)을 사용하여 person 객체의 다른 필드들은 그대로 복사하고, artwork 필드만 새롭게 수정합니다. artwork 객체 안에서는 city 값만 'New Delhi'로 수정하고, 나머지 값은 그대로 유지됩니다.
DEEP DIVE
객체들은 사실 중첩되어 있지 않습니다.
음.. 위에서 중첩된 객체라고 하며 설명했는데 갑자기 이게 무슨 말일까요..?
"중첩된 객체"라고 하면 보통 한 객체 안에 또 다른 객체가 포함된 형태, 아래 코드와 같은 모습을 떠올리기 쉽습니다.
아래 코드에서는 obj라는 객체 안에 또 다른 artwork 객체가 포함되어 있습니다. 이 구조가 "중첩된 객체"처럼 보이기도 합니다.
let obj = {
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
};
하지만 실제로 자바스크립트에서 객체는 "참조"로 다뤄진다는 점을 이해해야 합니다. 그래서 "중첩된 객체"라는 표현은 정확하지 않을 수 있습니다. 왜냐하면, 사실 두 객체가 같은 객체를 참조하기 때문에 한 객체를 변경하면 다른 객체도 영향을 받게 되기 때문입니다.
아래 코드에서는 obj2는 artwork 프로퍼티로 obj1 객체를 참조하고 있습니다. 이 말은 obj2.artwork는 사실 obj1과 같은 객체를 가리키고 있다는 뜻입니다.
let obj1 = {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
};
let obj2 = {
name: 'Niki de Saint Phalle',
artwork: obj1 // obj1을 참조
};
아래 코드에서 obj3도 obj1을 참조합니다. 이 말은 obj1, obj2.artwork, obj3.artwork가 모두 동일한 객체를 가리키고 있다는 뜻입니다.
let obj3 = {
name: 'Copycat',
artwork: obj1 // obj1을 참조
};
따라서 이 객체들 중 하나의 값을 변경하면, 그 객체를 참조하는 다른 객체에도 영향을 미친다는 것을 알 수 있습니다.
예를 들어, obj3.artwork.city를 New Delhi로 변경하려고 합니다.
obj3.artwork.city = 'New Delhi';
이렇게 obj3.artwork.city를 변경하면, obj2.artwork.city와 obj1.city도 함께 변경됩니다. 왜냐하면 obj2.artwork, obj3.artwork, obj1은 모두 같은 artwork 객체를 참조하고 있기 때문입니다.
따라서, 객체를 중첩된 것으로 생각하면 이해가 어려울 수 있어 서로를 가리키는, 참조한다고 하는 것이 더 정확합니다.
마지막으로 Immer로 간결한 갱신 로직 장성하기입니다.
Immer? 저 정말 처음 들어봐서 Immer에 대해 먼저 알아보겠습니다.
Immer
Immer는 불변성(immutable)을 지키면서 상태 변경을 더 쉽게 해주는 라이브러리입니다. React에서 상태를 관리할 때, 객체나 배열을 다룰 때 불변성을 지켜야 하는데, Immer를 사용하면 불변성을 유지하면서도 객체를 편리하게 변경할 수 있습니다.
- draft는 Immer가 제공하는 임시 상태 객체입니다. 이 객체는 실제로 상태를 변경하는 작업을 하는 동안 사용할 수 있고 작업용 초안이라고 생각하면 쉽습니다. draft를 통해 상태를 자유롭게 수정할 수 있지만, 실제로 변경이 완료되는 것은 아닙니다. 최종적인 변경은 draft에서 작업을 마친 후에 새로운 상태 객체로 생성됩니다.
- Proxy는 자바스크립트의 내장 객체 중 하나로, 다른 객체에 대한 "중개자" 역할을 합니다. Proxy 객체를 사용하면, 특정 객체에 접근하거나 수정할 때, 그 과정이나 동작을 조작하거나 기록할 수 있습니다.
- Proxy 객체는 우리가 draft에서 객체를 수정할 때, 그 변경된 부분을 추적하고 기록합니다.
- Proxy를 사용하면 객체를 직접 수정하는 것처럼 보이지만, 실제로는 불변성을 유지할 수 있습니다.
DEEP DIVE
Immer는 어떻게 작동할까요?
Immer가 제공하는 draft는 Proxy라고 하는 아주 특별한 객체 타입으로, 당신이 하는 일을 “기록” 합니다.
- draft
- draft는 Immer 라이브러리에서 제공하는 특별한 객체로, 상태를 임시로 수정할 수 있는 "작업용 초안"이라고 생각하면 됩니다.
- 실제 상태 객체를 직접 수정하지 않고, draft에서 상태를 수정하며 최종적으로 변경된 내용을 새로운 객체로 반환합니다.
- 즉, draft는 상태 변경 작업을 위해 사용되는 임시 객체입니다.
- Proxy
- Proxy는 자바스크립트 내장 객체로, 다른 객체를 감싸서 그 객체에 대한 액세스나 수정을 제어하는 역할을 합니다.
- Proxy 객체는 우리가 객체의 속성에 접근하거나 수정할 때, 그 과정이나 동작을 기록하거나 변경할 수 있습니다.
- Proxy를 사용하면 실제 객체를 수정하는 것처럼 보이지만, 내부적으로는 수정이 추적되고 기록되며 불변성을 유지할 수 있습니다.
- "기록"이라는 것은, 객체에서 어떤 속성이 변경되었는지, 그리고 그 변경이 무엇인지 추적하는 과정을 의미합니다. 이 과정을 통해 상태 변경이 불변성을 유지하며 안전하게 이루어집니다.
객체를 원하는 만큼 자유롭게 변경할 수 있는 이유죠! Immer는 내부적으로 draft의 어느 부분이 변경되었는지 알아내어, 변경사항을 포함한 완전히 새로운 객체를 생성합니다.
- draft는 사용자가 상태를 자유롭게 변경할 수 있는 작업용 객체로, 실제 상태 객체를 건드리지 않고 임시 객체에서 수정 작업을 진행할 수 있습니다.
- Immer는 Proxy를 활용하여 draft에서 어떤 부분이 변경되었는지 추적합니다. 변경된 부분만을 포함한 새로운 객체를 생성함으로써, 불변성을 유지하고 효율적인 상태 업데이트가 가능합니다.
- 변경된 부분만 추적하고 새로운 객체를 만들어내기 때문에, 불필요한 수정 없이 최소한의 변경만을 적용하여 성능을 최적화합니다.
아래 코드에서 Immer는 updatePerson이라는 함수를 제공하고, 이 함수에 넘겨주는 draft 객체를 직접 수정할 수 있습니다. 이렇게 작성하면 상태를 업데이트할 때마다 새로운 객체를 만들고 이전 상태를 덮어쓰는 것처럼 보이지만, 실제로는 불변성을 지키면서 상태를 안전하게 갱신하는 방식으로 동작합니다.
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 updateCity() {
updatePerson(draft => {
draft.artwork.city = 'Lagos'; // draft 객체에서 city를 수정
});
}
return (
<>
<button onClick={updateCity}>Update City</button>
<p>{person.artwork.city}</p>
</>
);
}
- useImmer는 상태를 관리하는 훅으로, 상태가 변경될 때마다 새로운 객체를 반환합니다.
- draft 객체는 Proxy 객체로, 내부에서 상태를 변경하는 과정을 추적하고 기록합니다. draft는 임시 객체로 상태를 수정하며, 변경 사항만을 반영하여 새로운 객체를 생성합니다.
- Immer는 이 방식으로 불변성을 유지하면서도 직관적으로 상태를 갱신할 수 있도록 도와줍니다.
DEEP DIVE
왜 React에서 state 변경은 권장되지 않나요?
- 디버깅의 어려움: state를 직접 변경하면 이전 state와 변경된 state의 차이를 추적하기 어려워지며, console.log 등을 통한 상태 변화 추적이 어려워집니다.
- 최적화: React의 최적화 전략은 이전 state와 새로운 state가 같으면 렌더링을 건너뛰는 것입니다. 직접 변경된 state는 비교할 때 성능 최적화에 문제가 될 수 있습니다.
- React 기능 사용 제한: React의 새로운 기능들은 상태의 불변성을 전제로 하며, state를 직접 변경하면 이 기능들을 사용할 수 없을 수 있습니다.
- 기능 확장 용이성: 상태의 이전 값을 보관하고 복원하는 기능(예: Undo/Redo)이 필요할 때, state가 불변일 경우 더 쉽게 구현할 수 있습니다.
- 단순한 구현: React는 상태를 직접 변경하지 않고 새로운 값을 할당하는 방식으로 동작하므로, 더 간단하고 효율적인 상태 관리를 할 수 있습니다.
따라서 state를 직접 변경하는 것은 권장되지 않으며, 불변성을 유지하는 방식으로 상태를 관리하는 것이 React의 최적화 및 미래 기능을 제대로 활용할 수 있는 방법입니다.
하나의 챕터라도 차근차근 꼼꼼하게 이해하며 정리하자! 라는 생각으로 적다 보니 글이 조금 길어졌네요. 사진 없이 글만 가득한 밋밋한 아티클이지만, 끝까지 읽어주셔서 감사합니다 :)
이번 챕터에서 Immer에 대해 처음 보고 다뤄보는 경험이 있었는데, 실제로 Immer를 사용해본 경험이 있으신 분이 있는지 궁금합니다. 혹시 Immer가 유용했던 코드나 상황이 있었다면, 어떤 점에서 도움이 되었는지도 궁금합니다!
그럼 진짜 끝입니다! 나리스 여러분 사랑합니다.. 파이팅~
'나야, 리액트 스터디' 카테고리의 다른 글
[week 4] 객체 state 업데이트하기, 배열 state 업데이트하기 (3) | 2024.11.17 |
---|---|
[week4] 객체 state 업데이트 하기, 배열 state 업데이트하기 (3) | 2024.11.17 |
[week3]- 이벤트처리 (4) | 2024.11.11 |
[week3] - 이벤트에 응답하기, State: 컴포넌트의 기억 저장소, 랜더링 그리고 커밋, 스냅샷으로서의 state (2) | 2024.11.10 |
[week3] 이벤트 핸들러 및 전파, State (2) | 2024.11.10 |