상태 불변성이란 한번 생성된 상태(state)를 직접 수정하지 않고, 상태를 변경해야 할 때는 항상 원본 상태를 기반으로 새로운 상태 객체를 생성하여 대체하는 프로그래밍 원칙.

JS는 기본적으로 어떠한 값을 저장할 때,
원시 타입의 경우 값이 콜 스택에 저장되고
참조 타입의 경우 실제 값은 메모리 힙, 그 값을 저장한 메모리 힙주소 값은 콜 스택에 저장된다.
React는 상태 변화 감지를 얕은 비교를 통해 수행하며,
변화 감지 기준은 콜 스택의 주소값입니다.
즉, [1,2,3] 배열(참조 타입)에서 0번째 index수를 4로 바꾸었다 하더라도 ( [4,2,3]) 메모리 힙에서의 값이 바뀔 뿐 콜 스택에서의 주소값은 변화가 없기 때문에 React는 변화를 감지하지 못합니다.
얕은 비교의 경우 주소값만을 비교하기 때문에 시간복잡도 O(1)의 시간이 소요되지만, 깊은 비교의 경우 주소값 내부의 값을 비교하기 때문에 O(N)의 시간이 소요되어 상대적으로 더 많은 시간이 걸린다.
let A = "Hello" // 주소 AA : "Hello"
A = "Bye" // 주소 BB: "Bye"
원시 타입의 경우 값 상태 변화시 새로운 주소값 생성 후 변화된 값을 저장하기 때문에 콜 스택에서 변화를 감지할 수 있다.
let arr = [1,2,3] // 주소값 500 : [1, 2, 3]
arr[0] = 4 // 주소값 500 : [4, 2, 3]
arr = [...arr] // 주소값 600 : [4, 2, 3]
참조 타입의 경우 값을 변화하면 주소값이 가지고 있는 값의 변화만 발생하여 콜 스택에서의 주소값은 변하지 않아 React에서 변화 감지를 하지못한다. 하지만 spread 연산을 통해 값을 새 배열을 생성하면 주소값이 변경되면서 상태 변화를 감지할 수 있다.

새로운 주소 값 생성 후, 값을 할당 받은 경우 이전에 사용했던 주소 값은 Garbage Collection(가비지 컬렉션)에 의해 불필요한 메모리 공간이 정리된다.
그렇기 때문에 React에서 참조 타입 수정 시 map, filter, reduce, spread 연산자 등을 활용해서 값을 수정한다.
// map을 사용하여 모든 요소를 2배로 만든 새 배열 생성
const doubled = numbers.map(num => num * 2);
// 배열에 새 요소 추가
const originalArray = [10, 20, 30];
const newArray = originalArray.concat([40, 50]);
// 특정 요소 업데이트
const tasks = [
{ id: 1, name: "운동", completed: false },
{ id: 2, name: "공부", completed: false },
{ id: 3, name: "청소", completed: true }
];
const updatedTasks = tasks.map(task =>
task.id === 2 ? { ...task, completed: true } : task
);
// 일반적인 방식
setCount(count + 1);
// 함수형 업데이트 (더 안전함)
setCount(prevCount => prevCount + 1);
React의 setState나 useState의 setter 함수가 함수형 업데이트를 지원하며, 이를 통해 이전 상태 기반으로 새 상태를 안전하게 생성할 수 있다
function handleClick() { setCount(count + 1); // count가 0이라면 1로 설정 setCount(count + 1); // 여전히 count는 0이므로 1로 설정 setCount(count + 1); // 여전히 count는 0이므로 1로 설정 } function handleClick() { setCount(prevCount => prevCount + 1); // 0 -> 1 setCount(prevCount => prevCount + 1); // 1 -> 2 setCount(prevCount => prevCount + 1); // 2 -> 3 }