1. 변이(Mutation)란 무엇인가
- state 는 javaScript 의 어떤 값이든 담을 수 있다
- 이는 객체도 담을 수 있다는 것.
- 기술 적으로 객체 자체의 내용을 변경하는 것은 가능하다
const [p, setP] = useState({x: 0, y: 0})
p.x = 5
- 위 로직은 자바스크립트에서 가능하다.
- React state의 객체는 기술적으로 위와 같이 변이할 수 있지만, 이는 원시 타입과 같이 불변하는 것처럼 취급해야 한다.
2. state 를 읽기 전용으로 취급할 것
- 즉, state 에 넣는 모든 값을 read-only 로 취급해야 한다
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>
);
}
- 이 코드에서 문제가 되는 건 onPointerMove 다.
- 이 코드는 이전 렌더링에서 position 에 할당된 객체를 수정한다.
- 하지만 setPosition 함수를 사용하지 않으면 React 는 객체가 변이됐다는 사실을 알지 못한다. 즉 Trigger 를 하지 못한다.
- 그렇기 때문에 리렌더링을 촉발하기 위해서는 새 객체를 생성하고 setPosition 함수에 그 값을 전달해야 한다
onPointerMove={e => {
setPosition({
x: e.clientX,
y: e.clientY
});
}}
3. 전개 구문을 사용하여 객체 복사하기
- 보통, 기존 데이터를 새로 만드는 객체의 일부로 포함시키고 싶은 경우가 있다
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>
</>
);
}
- 예를 들어 이 코드에서 원하는 동작을 위해서는 새 객체를 생성하고, setPerson 에 전달해야 한다. 하지만, 그러기에는 변하지 않는 객체의 값들이 많아서 코드를 많이 작성하기가 불편하다.
setPerson({
firstName: e.target.value, // input에서 받아온 새로운 first name
lastName: person.lastName,
email: person.email
});
- 이럴 때 전개 구문을 사용한다
setPerson({
...person, // 이전 필드를 복사합니다.
firstName: e.target.value // 중복된 key 인 firstName 만 덮어 씌운
});
4. 중첩된 객체 업데이트하기
- 만약, 객체가 중첩된 경우, 어떻게 해야할까?
- spread 는 딱 한 depth 의 객체만 깊은 복사하기 때문에 그 너머의 depth 의 값들은 얕은 복사 (주소를 복사)한다.
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: '<https://i.imgur.com/Sd1AgUOm.jpg>',
}
});
- 만약 이런 상황이라면
const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);
// 혹은
setPerson({
...person,
artwork: {
...person.artwork,
city: 'New Delhi'
}
});
- 이런 식으로 객체 내부의 객체까지 깊게 복사해야 한다.
Immer 사용하기
- state 가 깊게 중첩된 경우, Immer 를 사용할 수 있다.
updatePerson(draft => {
draft.artwork.city = 'Lagos';
});
- 마치 변이시키는 것 같지만, 실제로는 이전 state 를 덮어 쓰는 것이 아니다.
- Immer 에서 제공하는 draft 는 Proxy 라는 객체로, 사용자가 수행하는 작업을 기록한다. 그렇기 대문에 Immer는 내부적으로 draft 의 어떤 부분이 변경됐는지를 파악하고 편집 내용이 포함된 완전히 새로운 객체를 생성한다.
왜 React 에서 state 변이를 권장하지 않는가?
1. 디버깅
- console.log 를 사용하고 state를 변이하지 않으면 과거 기록이 최근 state 변이에 의해 지워지지 않는다
- 그렇기에 렌더링 사이에 state 가 어떻게 변경됐는지 명확하게 알 수 있다.
2. 최적화
- React 최적화 전략은 이전 프로퍼티나 state 가 다음 프로퍼티나 state 와 동일한 경우, 작업을 건너뛰는 것에 의존한다
- state 를 변이하지 않는다면 변경이 있었는지 확인하는 것이 매우 빠르다.
- 만약 prevObj === obj 라면, 내부에 변경된 것이 없음을 확신할 수 있다.
- 즉, 주소값이 같은지, 아닌지만 따지면 될 뿐. 내부의 내용까지 일일이 하나씩 확인할 필요가 없다는 것
3. 새로운 기능
- React 에서 개발중인 기능은 state가 스냅샷처럼 취급되는 것에 의존한다
- 과거의 state 를 변이하는 경우, 새로운 기능을 사용하지 못할 수 있다.
4. 요구사항 변경
- 실행 취소, 다시 실행, 변경 내역 표시 등등 사용자가 양식을 이전 값으로 재설정할 수 있도록 하는 것과 같은 일부 애플리케이션 기능은 아무것도 변이되지 않은 state 에서 더 쉽게 수행할 수 있다.
- 과거의 state 복사본을 메모리에 보관하고 필요할 때 재사용할 수 있기 때문
- 변이가 가능한 방식으로 시작하면 이와 같은 기능을 추가하기 어려울 수 있다.
5. 더 간단한 구현
- React 는 변이에 의존하지 않으므로 객체에 특별한 작업을 할 필요가 없다.
- 많은 반응형 솔루션처럼 프로퍼티를 가로채거나, 항상 프록시로 감싸거나, 초기화할 때 다른 작업을 할 필요도 없다
- 이것이 React 를 사용하면 추가 성능이나 정확성의 함정 없이 아무리 큰 객체라도 state 에 넣을 수 있는 이유다.
Reference
https://react.dev/learn/updating-objects-in-state
Updating Objects in State – React
The library for web and native user interfaces
react.dev
https://react-ko.dev/learn/updating-objects-in-state
객체 state 업데이트 – React
The library for web and native user interfaces
react-ko.dev
'(준)공식 문서 > React' 카테고리의 다른 글
[ React 공식문서 ] state 관리하기 (1) : state 로 입력에 반응하기 (0) | 2024.03.06 |
---|---|
[ React 공식문서 ] state 관리하기 (0) : 훑어보기 (0) | 2024.03.06 |
[ React 공식문서 ] 상호작용 추가하기 (5) : 여러 state 업데이트를 큐에 담기 (0) | 2024.03.05 |
[ React 공식문서 ] 상호작용 추가하기 (4) : 스냅샷으로서의 State (0) | 2024.03.05 |
[ React 공식문서 ] 상호작용 추가하기 (3) : 렌더링하고 커밋하기 (0) | 2024.03.05 |