1. 객체 불변성이란?
객체의 상태를 직접 변경하지 않고, 새로운 객체를 생성하여 상태를 업데이트하는 원칙을 의미한다. React와 같은 라이브러리에서 상태 관리 시 불변성을 유지하는 것이 매우 중요하다.
한번 만들어진 state는 변경되지 않는다. 만약 값이 변경이 된다면 state가 새로운 값으로 생기는 것이므로, 원본이 변경 되면 안된다. 하지만 얕은 참조를 하게 되면 원본도 같이 변경된다.
- 한 번 생성된 state는 직접 변경되지 않아야 하며, 변경이 필요한 경우에는 새로운 객체를 생성하여 변경된 값을 담아야 한다.
- React에서는 상태를 직접 변경하면 상태 변경 감지가 제대로 이뤄지지 않기 때문에 불변성을 유지해야 한다.
불변성 안되는 코드(상태 변경이 어렵다)
const address = user.extra.addressBook.find((address) => address.id === Number(event.target.name));
address.value = event.target.value;
const newState = { ...user };
예를 들면 위 코드가 있다. 이는 이제, 아이디 값을 비교하여 변경을 값을 감지하고, 이를 바탕으로 수정하는 것이다. 근데 여기에 문제가 있다.
1. console.log('user', user === newState); // 기대값이 false
2. console.log('user.extra', user.extra === newState.extra); // false
3. console.log('user.extra.addressBook', user.extra.addressBook === newState.extra.addressBook);
4. console.log('회사', user.extra.addressBook[0] === newState.extra.addressBook[0]); // false
5. console.log('집', user.extra.addressBook[1] === newState.extra.addressBook[1]);
6. console.log('회사 주소', user.extra.addressBook[0].value === newState.extra.addressBook[0].value); // false
7. console.log('집 주소', user.extra.addressBook[1].value === newState.extra.addressBook[1].value);
8. console.log('기존 회사 주소', user.extra.addressBook[0].value);
이런식으로 비교를 하게 된다면 원래는 기대값이 위에 나와있듯이 1번은 이제 기존 객체와 새로운 객체 비교인데, 서로 다른 객체이기에 false로 나온다. 하지만 2번에서는 서로 다른 객체이지만 true로 나오며, 4번도 마찬가지이다. 분명 다른 값으로 입력을 했지만 같은 값으로 인식하여 true가 나온다.
이는 이제 기존 코드에서 상태를 직접 변경하고 있으며, React에서 상태 불변성을 깨뜨려서 위와 같은 결과가 나오는 것이다. 그래서 불변성을 유지하지 않으면 상태 변경 감지가 제대로 작동되지 않는다.
그래서 위 코드 말고, 새로운 방식이 있다. 새로운 객체를 생성하여 상태 업데이트 하는 것인데
불변성이 유지된 코드
const newAddressBook = user.extra.addressBook.map((address) => {
if (address.id === Number(event.target.name)) {
return { ...address, value: event.target.value };
} else {
return address;
}
});
- map을 사용하여 addressBook의 각 객체를 순회하면서, 조건에 맞는 새로운 값을 덮어 씌우고 나머지를 그대로 유지시킨다.
- 객체를 직접 수정하지 않고, 조건에 맞는 객체를 복사 { ...address, value: event.target.value }; 한 후 새로운 값을 설정한다.
- 그래서 값 변경이 되지 않으면 그대로 리턴하는 것이다.
여기에 이제 상위 객체도 새로운 참조로 복사하는 것인데,
const newState = {
...user,
extra: {
...user.extra,
addressBook: newAddressBook,
},
};
addressBook이 새로운 배열로 생성되었기 때문에, React 에서 이를 감지하려면 상위 객체인 extra와 user도 복사하면서 새로운 참조를 만들어야 한다. 이렇게 하면 상태의 불변성을 유지하면서 React에서 상태 변경을 감지할 수 있다.
2. 라이브러리를 통한 객체 불변성 유지
라이브러리 immer
immer는 Proxy 객체를 활용하여 아래와 같은 동작을 수행한다.
1. 초기 상태를 기반으로 가상적인 초안(draft) 객체를 생성
- produce 함수는 원본 객체를 기반으로 proxy를 통해 감싸진 draft 객체를 제공한다.
- 이 draft 객체는 원본 객체처럼 동작하지만, 직접적으로 값을 수정하면 immer가 내부적으로 변경된 부분만 기록한다.
2. 변경된 부분만 기록
- draft 객체에서 수정된 부분은 내부적으로 패치(patch)로 기록된다.
- 이후 변경된 부분만 새로운 객체로 복사된다.
- 변경되지 않은 부분은 원본 객체의 참조를 그대로 재사용한다.
3. 최종 결과물로 불변성을 유지하는 새로운 객체 생성
- produce 함수는 수정된 부분이 반영된 새로운 객체를 반환하며, 원본 객체는 변경되지 않는다.
// immer를 사용해서 불변성 유지
// user를 복사한 새로운 객체(draft)를 만들어서 반환
const newState = produce(user, (draft) => {
const address = draft.extra.addressBook.find((address) => address.id === Number(event.target.name));
ddress.value = event.target.value;
});
위 코드를 이제 아까 콘솔을 찍어보면 또 다른 결과 나온다.
1. console.log('user', user === newState); // 기대값이 false
2. console.log('user.extra', user.extra === newState.extra); // false
3. console.log('user.extra.addressBook', user.extra.addressBook === newState.extra.addressBook);
4. console.log('회사', user.extra.addressBook[0] === newState.extra.addressBook[0]); // false
5. console.log('집', user.extra.addressBook[1] === newState.extra.addressBook[1]);
6. console.log('회사 주소', user.extra.addressBook[0].value === newState.extra.addressBook[0].value); // false
7. console.log('집 주소', user.extra.addressBook[1].value === newState.extra.addressBook[1].value);
8. console.log('기존 회사 주소', user.extra.addressBook[0].value);
아래와 같이 기본 객체와 새로운 객체에 비교에 있어 false로 나오고, 또한 주소 변경에 있어서도 false로 나온다.
그래서 이를 봤을 때, 기존 흔히 말하는 얕은 복사로 하는 것이 아닌, 이런식으로 새로운 객체지만, 업데이트 변경되는 값들을 원본과 다르게 변경이 되게 해야하는 것 같다. 원본도 같이 변경이 된다면 그 말하는 불변성을 지키지 못할 뿐만 아니라 상태 변경에 대해서 좋지 않다고 한다.. 사실 아직은 크게 와닿지 않아 이는 프로젝트나 다른 실습을 해보면서 몸소 깨우쳐봐야겠다.
처음에는 재미있었는데, 점점 어려워지니... 공부좀 빡세게 해야겠다.
'React > 다시 공부하는 리액트' 카테고리의 다른 글
React clearInterval() (0) | 2024.11.20 |
---|---|
React prop-types (1) | 2024.11.19 |
React 코드 컨벤션 (0) | 2024.11.17 |
리액트 기본 개념 - 4 (2) | 2024.11.12 |
리액트 기본 개념 - 3 (2) | 2024.11.11 |