Redux와 상태 불변성 유지
Redux를 이용해 상태 관리를 하다 보면, 특히 불변성을 유지하는 것이 중요합니다. 기존 useState 기반의 컴포넌트 로컬 상태를 Redux를 통해 전역 상태로 관리하려고 할 때, 이 불변성 원칙을 놓치는 경우 오류가 발생할 수 있습니다. 이번 글에서는 전역 상태인 inquiryList를 체크박스의 상태와 연결하고 이를 안전하게 업데이트하는 방법을 설명합니다.
문제 상황: 불변성 원칙 위배
inquiryList는 string[] 타입의 전역 상태로, 체크박스 선택 시 이를 업데이트하기 위해 handleCheckItem이라는 함수를 사용하고 있습니다.
const handleCheckItem = (value: string, isChecked: boolean) => {
let copy = inquiryList;
if (isChecked) {
copy.push(value);
} else if (!isChecked && inquiryList.includes(value)) {
copy = copy.filter(e => e !== value);
}
dispatch(updateInquiryList(copy));
}
그러나 초기 코드에서 다음과 같은 문제가 발생했습니다:
이 코드에서 copy 변수는 useSelector를 통해 가져온 전역 상태 inquiryList를 참조하고 있습니다. 문제는 Redux의 상태는 불변성을 지켜야 한다는 점입니다. let copy = inquiryList에서 copy는 실제 상태 값이 아닌, 그 상태가 저장된 메모리 주소를 참조하고 있습니다. 이는 일명 "얕은 복사"이며, 실제로는 원본 배열에 대한 참조만을 제공하게 됩니다.
따라서 copy.push(value)와 같은 조작을 시도할 때, 이는 원본 상태인 inquiryList를 직접 수정하려는 움직임으로 간주됩니다. Redux는 상태의 직접적인 변경을 허용하지 않기 때문에, 불변성 원칙을 위배하면서 다음과 같은 오류가 발생하게 됩니다:
Cannot add property 0, object is not extensible
해결 방법: 깊은 복사 사용
이 문제를 해결하기 위해서는 "깊은 복사"를 이용해야 합니다. 깊은 복사를 통해 기존 상태의 값 자체를 복사하여 새로운 객체나 배열을 생성하면 불변성을 유지하면서 안전하게 상태를 업데이트할 수 있습니다. inquiryList는 1차원 배열이므로 전개 연산자(Spread Operator)를 사용하여 쉽게 깊은 복사를 할 수 있습니다.
수정된 코드
다음과 같이 수정된 코드를 보면, 깊은 복사를 통해 새로운 배열을 생성하는 방식으로 문제가 해결됩니다:
const handleCheckItem = (value: string, isChecked: boolean) => {
let copy = [...inquiryList]; // 깊은 복사 수행
if (isChecked) {
copy.push(value);
} else if (!isChecked && inquiryList.includes(value)) {
copy = copy.filter(e => e !== value);
}
dispatch(updateInquiryList(copy));
}
let copy = [...inquiryList]는 inquiryList의 모든 요소를 복사하여 새로운 배열을 생성합니다. 이렇게 하면 원본 상태를 직접 수정하지 않으므로 Redux의 불변성 원칙을 준수할 수 있습니다. 전개 연산자는 배열이 1차원일 때 간단하고 효과적인 깊은 복사 방법으로 활용될 수 있습니다.
결론
정리하자면, Redux 상태 관리를 할 때 불변성을 유지하기 위해서는 얕은 복사 대신 깊은 복사를 활용해야 하며, 특히 전개 연산자는 간단한 구조의 배열이나 객체를 다룰 때 매우 유용합니다. 이러한 방식으로 상태를 안전하게 관리하여 오류 없이 Redux의 강점을 활용할 수 있습니다.