의도: 무의식적으로 사용하고 있던 개념에 대해 확인하는 질문
나의 답안
리액트에서 상태가 불변성을 유지하는 이유는 상태 변경 감지와 효율적인 렌더링을 위해서입니다.
리액트는 상태가 바뀌면 컴포넌트를 다시 렌더링하는데,
이때 '바뀌었는지'를 판단하기 위해 얕은 비교(shallow comparison)를 사용합니다.
즉, 객체의 내용 전체를 깊게 비교하지 않고 참조(reference)가 달라졌는지만 확인합니다.그래서 상태를 직접 수정하면 참조가 그대로 유지되어 리액트는 변경이 없다고 판단해서
렌더링이 일어나지 않거나, 예상과 다른 UI가 표시되는 버그가 발생할 수 있습니다.
반대로, 불변성을 지켜 새로운 객체나 배열을 만들어 상태를 갱신하면
참조가 달라지기 때문에 리액트가 정확하게 변경을 감지하고 필요한 부분만 다시 렌더링할 수 있습니다.
주어진 답안 (모범 답안)
리액트는 불변성을 유지하도록 설계되었습니다.
그래서 잘 아시다시피 상태를 변경하려면 직접 변경하는 것이 아니라, 새로운 객체를 만들어서 할당해주어야 합니다.
이러한 불변성을 유지하는 것에는 여러 이유가 있는데 그 중 대표적으로 불변성 덕분에 리액트는 상태가 언제 어떻게 변경되었는지 추적하기가 쉬워집니다.만약 직접 변경한다면 무엇이 언제 어떻게 변경되었는지 추적하기 쉽지 않을 것입니다.
리덕스나 리액트의 개발자 도구를 보면 상태가 어떻게 변화되었는지 찾아보는 게 굉장히 유용한데, 이게 다 불변성 덕분입니다.다만 불변성을 유지하는 게 개발자 입장에서는 어려울 수도 있는 일이라 immer 같은 라이브러리를 이용해 대신 불변성을 유지할 수 있도록 위임하는 일도 가끔 있습니다.
오히려 리덕스 툴킷에는 내장되어 있는 기능이기도 하니 불변성을 유지하다 보니 코드가 어지러워진다고 생각하면 도입을 적극 고려해야 한다고 생각합니다.
React에서 상태 불변성을 유지해야 하는 주된 이유는 성능 최적화와 에측 가능한 코드 작성 때문이다. 구체적으로 보면 다음과 같다.
React에서 상태를 불변으로 유지하려면, 기존 상태를 직접 변경하지 않고 새로운 상태 객체를 생성해야 한다. 이를 구현하는 방법은 다음과 같다.
객체의 상태 업데이트
객체를 업데이트할 때는 기존 객체를 복사한 뒤, 변경된 속성을 덮어쓴다.
const [state, setState] = useState({name: "John", age: 25});
// 상태 업데이트 시
setState({
...state, // 기존 상태를 얕게 복사
age: 26, // 복사된 객체에서 특정 속성만 업데이트
})
배열의 상태 업데이트
배열을 업데이트할 때는 map, filter, concat 등의 불변성을 유지하는 메서드를 사용한다.
배열에 새로운 항목 추가
const [items, setItems] = useState([1, 2, 3]);
setItems([...items, 4]); // 기존 배열을 복사하고 새 항목 추가
배열에서 항목 제거
setItems(items.filter(item => item !== 2)); // 조건에 맞는 항목 제거
배열의 특정 항목 수정
setItems(items.map(item => (item === 2 ? 20 : item))); // 조건에 맞는 항목 변경
Immer 라이브러리 사용
Immer는 상태를 불변으로 유지하는 로직을 단순화해주는 라이브러리이다.
Immer를 사용하면 기존 상태를 직접 수정하는 것처럼 작성하더라도 내부적으로 불변성을 유지한다.
npm install immer
import produce from "immer";
const [state, setState] = useState({ name: "John", age: 25 });
setState(produce(state, draft => {
draft.age = 26; // 상태를 직접 변경하는 것처럼 작성
}))
React에서 상태를 직접 수정하면 불변성이 깨지고, 예상치 못한 문제가 발생할 수 있다.
아래는 불변성을 깨뜨리는 사례와 이를 수정하는 방법이다.
state.age = 26; // 직접 수정 (잘못된 방법)
setState(state); // React는 변경을 감지하지 못함수정된 코드setState({
...state,
age: 26,
});items.push(4); // 배열 직접 수정 (잘못된 방법)
setItems(items); // React는 변경을 감지하지 못함수정된 코드setItems([...items, 4]);React의 상태 불변성은 상태 관리에서 핵심적인 개념이다.
불변성을 유지하면 성능과 코드의 예측 가능성이 향상되고, 디버깅이 쉬워진다.
React는 불변성을 보장하지 않으므로, 개발자가 직접 상태를 불변하게 관리해야 하며, 이를 위해 Spread 연산자, map() 함수 등의 내장 메서드, 또는 Immer 같은 도구를 활용하는 것이 좋다.