💥
React는 왜 불변성을 유지해야되나?
December 22, 2022
리액트의 상태 업데이트는 항상 불변적
으로 수행되어야 한다.
- mutate(돌연변이한)한 값의 대상과 위치에 따라 컴포넌트가 렌더링 되지 않을 수 있다.
- 데이터가 실제로 업데이트 된 시기와 이유에 대해 혼란을 겪을 수 있다.
불변성이란?
- Immutability(불변성)는 상태나 값을 변경하지 않는 것이다.
- JavaScript에서 예를 들자면,
- JavaScript의 원시 타입의 값들은
불변성
을 갖고 있다.- 데이터 타입
- 원시 타입(string, number, bigint, boolean, undefined, ES6 부터 추가된 symbol)
- 변경이 불가능한 값(읽기전용)
- 원시 값을 변수에 할당하면 확보된 메모리 공간(주소)에는 실제 값이 저장됨
- 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시값이 복사되어 전달된다.
- 참조 타입(Object, Array, Function)
- 변경이 가능한 값
- 객체를 변수에 할당하면 확보된 메모리 공간에 참조값이 저장됨
- 객체를 가르키는 변수를 다른 변수에 할당하면 원본의 참조값이 복사되어 전달된다.
- 원시 타입(string, number, bigint, boolean, undefined, ES6 부터 추가된 symbol)
- 즉, 원시타입은 재할당을 통해서만 변수에 저장한 값을 변경할 수 있지만, 객체타입은 재할당 없이 객체를 직접 동적으로 추가, 삭제 갱신 등이 가능하다는 것이 큰 차이점이다.
- 데이터 타입
- JavaScript의 원시 타입의 값들은
추가로 JavaScript에서 얕은 복사와 깊은 복사
- 얕은 복사(Shallow copy)
- 객체를 프로퍼티 값으로 갖는 객체의 경우에 한 단계 까지만 복사하는 것을 말한다.
- 즉, 첫 객체는 새로운 메모리 주소를 가지지만 내부에 있는 중첩된 객체는 얕은 복사를 한 경우에 같은 메모리 주소를 가진다는 의미가 된다.
- 얕은 복사 방법으로는
Object.assign()
,전개연산자(spread)
- 얕은 복사는 하위 중첩되어 있는 객체까지 복사하지 않으므로 공유에 의한 예기치 못한 결과를 얻을 수 있다.
- 깊은 복사(Deep copy)
- 하위에 중첩되어 있는 객체(배열 등)까지 모두 복사하는 것을 말함
- 즉, 원시 값처럼 완전히 새로운 메모리 공간을 차지하는 원시값처럼 복사본을 만든다.
- 깊은 복사 방법으로는
JSON.stringify()
,재귀함수
,lodash의 cloneDeep
- 결론으로 객체의 참조 특성으로 인해 복사하고자 하는 객체에 중첩된 객체타입이 있는지를 확인하고 그에 맞는 복사 방법을 사용해야 한다.
그래서 리액트 상태를 변경할 때 불변성을 지켜주어야 되는 이유?
- 리액트에서
불변성을 지켜주어야 되는 이유
는 리액트가 상태 업데이트를 하는 원리 때문이다. - 리액트는 상태 값을 업데이트 할 때
얕은 비교
로 상태 변경 여부를 체크하며, 수행한다. - 즉,
리액트는
객체의 속성 하나하나를 비교하는게 아니라참조 값만 비교하여 상태 변화를 감지
한다. - 이런 이유로 리엑트는 state(배열이나 객체)를
업데이트 할 때 setState()를 사용
하여배열
이나객체를 새로 생성
해 새로운 참조 값을 만들어상태를 업데이트
한다. - 그리고 리엑트에서 불변성을 지켜주면 다른 이점으로
사이드 이펙트를 방지
할 수 있다.
정리하자면
리액트는 불변성을 지켜주면 효과적인 상태 업데이트(얕은 비교 수행: 계산 리소스를 줄여줌)
와 사이드 이펙트를 방지하는 이점(원본 데이터를 건들게 될 경우, 원본 데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있으며, 프로그래밍의 복잡도가 올라간다.)
을 가질 수 있게 된다.
불변성을 지키며 상태를 업데이트 하는 방법은?
-
spread operator, map, filter, slice, reduce 등등
새로운 배열을 반환하는 메소드들을 활용
하거나- splice는 원본데이터를 변경하기 때문에 사용하면 안된다.
-
setState를 이용
할 경우- 원시타입인 경우에는 값을 바로 넣어서 사용해도 되지만
- 참조타입인 경우에는 새로운 객체나 배열을 생성한 후 값을 넣어주어야 한다.
// 원시타입 const [number, setNumber] = useState(0); setState(7); // 참조타입 const [person, setPerson] = useState({ name: '', age: 33 }); setState({ ...person, name: 'seahyun' });
정리
- 불변성이란 메모리 영역의 값을 변경하지 않는 것이다.
- 리액트는 불변성을 지켜줌으로써
효율적인 상태업데이트
를 한다. - 리액트는 불변성을 지켜줌으로써
사이드 이펙트를 사전 방지하고 프로그래밍의 구조를 단순하게 유지
한다. - 불변성을 가진 원시타입과 달리
참조타입의 경우에는 의도적으로 불변성을 지켜주어야
한다.방법으로는
새로운 주소 값을 가진 객체(배열)를 생성하여 상태를 업데이트 해주어야 한다. 또는 spread operator, map, filter, slice, reduce 메소드들을 이용하여 새로운 배열의 값을 반환하여 사용한다.