불변성 유지
-
리액트(React)에서 상태(state)를 업데이트할 때는 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야 한다.
-
이렇게 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 불변성을 지킨다고 한다.
-
불변성이란 메모리 영역의 값을 변경할 수 없는 것이다.
-
불변성 유지를 해 주어야 나중에 리액트(React) 컴포넌트(Component)의 성능을 최적화할 수 있다.
-
리액트는 불변성을 지켜줌으로써 효율적인 상태(state) 업데이트를 한다.
-
리액트는 불변성을 지켜줌으로써 사이드 이펙트를 사전 방지하고 프로그래밍의 구조를 단순하게 유지한다.
-
불변성을 가진 원시 타입(Primitive type)과 달리 참조 타입(Reference Type)의 경우에는 의도적으로 불변성을 지켜주어야한다.
-
이 때 새로운 주소 값을 가진 객체(Object)를 생성하여 상태를 업데이트 해준다.
-
확산 연산자(spread operator), map(), filter(), slice(), reduce() 메서드(Method)들을 활용한다.
-
업데이트가 필요한 곳에서는 아예 새로운 배열(Array) 혹은 새로운 객체(Object)를 만들기 때문에, React.memo()를 사용했을 때 props가 바뀌었는지 혹은 바뀌지 않았는지 알아내서 리렌더링(Re-rendering)성능을 최적화해줄 수 있다.
원시 타입(Primitive type)과 참조 타입(Reference Type)의 차이점
- 원시 타입(Primitive type)은 불변성을 가지고 있다.(값을 변경할 수 없다는 이야기가 아니다.)
- 대표적인 원시타입인 let string 타입을 통해 예를 들어보자.
JavaScript
- 변수 string은 data1 -> data2로 값이 변경되었다.
- 사실 위 문장은 반은 맞고 반은 틀리다.
- 위 예시를 보면 string 변수는 'data1' → 'data2' 로 값이 변경된 것처럼 보이지만 실제 메모리영역에는 'data1', 'data2' 둘다 존재한다.
- 메모리 영역이 1~10영역까지 10개가 있다고 가정해보자.
JavaScript
-
위 예시에서 메모리 영역을 총 2개 사용했다.
-
변수(Variable) string은 'data1' 였고, 여기에 'data2'를 재할당하였는데 기존 메모리 영역 1에 있는 'data1'의 값은 그대로 두고, 메모리 영역2에 'data2'를 새로 할당했다.
-
즉, 자바스크립트는 메모리영역에서 'data2'는 'data1'을 대체하는 것이 아니라 새로운 영역에 할당된다.
-
이것이 불변성이다.
-
이번엔 참조 타입을 통해서 불변성에 대해 알아보자.
JavaScript
-
array.push(5)는 원본 배열(Array)을 수정하면서 불변성을 지키지 않고 있고,
array = [1, 2, 3, 4]는 원본 배열을 수정하는게 아니라 새 참조값을 가진 새로운 배열[1, 2, 3, 4]을 할당하여 불변성을 지켜주고 있다. -
따라서 정리하자면, 불변성의 진짜 의미는 메모리 영역에서 값이 변하지 않는다라는 의미이다.
JSX
리액트에서 불변성을 지켜야 하는 이유
1. 리엑트의 작동 방식
- 리액트에서 불변성을 지켜주는 이유는 리액트가 상태(state)를 업데이트를 하는 원리 때문이다.
- 리액트는 상태값을 업데이트 할 때 얕은 비교(얕은 복사)를 수행한다.
- 즉 배열(Array)이나 객체(Object)의 속성(Property) 하나하나를 비교하는게 아니라 이전 참조값과 현재 참조값만을 비교하여 상태 변화를 감지한다.
- 이런 이유로 배열이나 객체를 업데이트 할때 setState
([...state, newState]), setState({...state, [key]: value})이런식으로 새로운 참조값을 가진 배열(Array)이나 객체(Object)를 생성하는 것이다. - 불변성을 지킴으로써 리액트는 상태변화를 감지할 수 있다.
2. 사이트 이펙트 방지
- 불변성을 지켜줌으로써 얻게 되는 또 다른 이점은 바로 사이드 이펙트를 방지하는 것이다.
- 즉 외부에 존재하는 원본데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용하기에 예상치 못한 오류를 사전에 방지할 수 있다.
- 다시 반대로 생각해보면 외부의 값을 함부로 변경할 수 있는 것은 위험한 일이다.
- 만약 다른 어떤 곳에서 원본데이터를 사용하고 있다고 하면 어플리케이션 어딘가에서 사이드 이펙트가 일어날 가능성이 있기 때문이다. (프로그램의 복잡성이 증가할 수 있다.)
- 결국 리액트는 불변성을 지킴으로 인해 효과적인 상태 업데이트와 사이드 이펙트를 방지하는 이점들을 얻고 있다.
정리
-
효율적인 상태업데이트 (얕은 비교 수행)
- 얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고, 객체의 참조 주소값만 변경되었는지 확인한다.
- 얕은 비교는 계산 리소스를 줄여주기 때문에 리액트는 효율적으로 상태를 업데이트 할 수 있다.
-
사이드 이펙트 방지 및 프로그래밍 구조의 단순성
- 원시타입은 애시당초 불변성 특징을 가지고 있지만 참조타입인 객체나 배열의 경우 값을 변경할 때 원본데이터가 변경될 여지가 있다. (불변성이 지켜지지 않을 수 있다.)
- 이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있다.
- 프로그래밍의 복잡도도 올라간다.
- 따라서 불변성을 지켜주면 사이드 이펙트를 방지하고 프로그래밍의 구조를 단순하게 유지할 수 있다.
불변성을 키는 방법
- 확산 연산자(spread operator), map(), filter(), slice(), reduce() 등등 새로운 배열(Array)을 반환하는 메서드(Method)들을 활용하면 된다.
- splice()는 원본데이터를 변경한다.(주의)
- setState를 이용할 때 원시타입 경우에는 값을 바로 넣어주어도 되지만 참조타입인 경우에는 새로운 객체(Object)나 배열(Array)을 생성한 후 값을 넣어주어야 한다.
JavaScript