들어가면서
리액트와 불변성의 연관 관계는 리액트가 지향하는 함수형 프로그래밍의 특징에서 발견할 수 있습니다. 함수형 프로그램밍의 특징 중 하나가 순수함수를 사용하는 것인데, 여기서 순수함수란 동일한 매개변수를 넣었을 때 동일한 리턴값을 출력하는 함수입니다. 동시에 순수함수는 외부의 값을 변경하는 사이드 이펙트가 일어나지 않는 조건을 지키는 함수를 뜻합니다. 여기서 외부의 값을 변경하지 않는다는 부분이 불변성과 깊이 연관된 부분입니다. 우선 불변성이란 개념을 이해하고 왜 지켜주는지 그 이유와 방법을 알아보겠습니다.
목차
1. 불변성이란 무엇일까
2. 왜 리액트에서 불변성을 지켜야하나
3. 어떻게 불변성을 지키는가
불변성이란 무엇일까
사전적으로 불변성이란 값이나 상태를 변경할 수 없는 것을 의미합니다.
하지만 위 문장만 읽고 불변성의 의미가 정확히 이해되지 않았습니다.
좀 더 불변성의 의미를 깊이 파악하기 위해 자바스크립트의 원시타입을 통해 불변성을 설명해보겠습니다.
원시타입 vs 참조타입
원시타입 vs 참조타입
원시타입: Boolean, String, Number, null, undefined, Symbol
참조타입: Object, Array
원시타입: 고정된 크기로 메모리에 저장, 실제 데이터가 변수에 할당.
참조타입: 데이터 크기가 정해지지 않고 메모리에 저장, 데이터의 값이 heap에 저장되며 변수에 heap 메모리의 주소값이 할당.
원시타입은 불변성을 가지고 있다고 했습니다. 그럼 값을 변경할 수 없는 걸까요?
대표적인 원시타입인 string 타입을 통해 예를 들어보겠습니다.
let string = 'data1'
string = 'data2'
변수 string은 data1 -> data2로 값이 변경되었습니다.
잠깐! 사실 위 문장은 반은 맞고 반은 틀립니다. 네???
위 예시를 보면 string 변수는 'data1' → 'data2' 로 값이 변경된 것처럼 보입니다.
하지만 실제 메모리영역에는 'data1', 'data2' 둘다 존재합니다.
메모리 영역이 1~10영역까지 10개가 있다고 가정해볼게요.
let string = 'data1' // 1. string: 'data1'가 메모리 영역1에 등록됩니다.
string = 'data2' // 2. string: 'data2'가 메모리 영역2에 등록됩니다.
위 예시에서 메모리 영역을 총 2개 사용했습니다.
변수 string은 'data1' 였고, 여기에 'data2'를 재할당하였는데
기존 메모리 영역 1에 있는 'data1'의 값은 그대로 두고,
메모리 영역2에 'data2'를 새로 할당했습니다.
즉, 메모리영역에서 'data2'는 'data1'을 대체하는 것이 아니라 새로운 영역에 할당됩니다.
이게 불변성입니다.
이번엔 참조 타입을 통해서 불변성에 대해 알아보겠습니다.
let array = [1, 2, 3, 4] // 메모리영역 1
array.push(5) // 메모리영역 1
array = [1, 2, 3, 4] // 메모리영역 2 (새로운 참조값)
array.push(5)는 원본 배열을 수정하면서 불변성을 지키지 않고 있고, array = [1, 2, 3, 4]는 원본 배열을 수정하는게 아니라 새 참조값을 가진 새로운 배열 [1, 2, 3, 4]을 할당하여 불변성을 지켜주고 있습니다.
정리하자면,
"불변성의 진짜 의미는 메모리 영역에서 값이 변하지 않는다 라는 의미입니다."
왜 리액트에서 불변성을 지켜야하나?
리액트에서 불변성을 지켜주는 이유는 리액트가 상태 업데이트를 하는 원리 때문입니다. 리액트는 상태값을 업데이트 할 때 얕은 비교를 수행합니다. 즉 배열이나 객체의 속성 하나하나를 비교하는게 아니라 이전 참조값과 현재 참조값만을 비교하여 상태 변화를 감지합니다. 이런 이유로 배열이나 객체를 업데이트 할때 setState([...state, newState]), setState({...state, [key]: value}) 이런식으로 새로운 참조값을 가진 배열이나 객체를 생성하는 것입니다. 불변성을 지킴으로써 리액트는 상태변화를 감지할 수 있습니다.
불변성을 지켜줌으로써 얻게 되는 또 다른 이점은 바로 사이드 이펙트를 방지하는 것입니다. 즉 외부에 존재하는 원본데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용하기에 예상치 못한 오류를 사전에 방지할 수 있습니다. 다시 반대로 생각해보면 외부의 값을 함부로 변경할 수 있는 것은 위험한 일입니다. 만약 다른 어떤 곳에서 원본데이터를 사용하고 있다고 하면 어플리케이션 어딘가에서 사이드 이펙트가 일어날 가능성이 있기 때문입니다. 결국 리액트는 불변성을 지킴으로 인해 효과적인 상태 업데이트와 사이드 이펙트를 방지하는 이점들을 얻고 있습니다.
1. 효율적인 상태업데이트 (얕은 비교 수행)
얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고, 객체의 참조 주소값만 변경되었는지 확인합니다. 얕은 비교는 계산 리소스를 줄여주기 때문에 리액트는 효율적으로 상태를 업데이트 할 수 있습니다.
2. 사이드 이펙트 방지 및 프로그래밍 구조의 단순성.
원시타입은 애시당초 불변성 특징을 가지고 있지만 참조타입인 객체나 배열의 경우 값을 변경할 때 원본데이터가 변경될 여지가 있습니다. (불변성이 지켜지지 않을 수 있습니다). 이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있습니다. 프로그래밍의 복잡도도 올라갑니다. 따라서 불변성을 지켜주면 사이드 이펙트를 방지하고 프로그래밍의 구조를 단순하게 유지할 수 있습니다.
어떻게 불변성을 지키는가?
spread operator, map, filter, slice, reduce 등등 새로운 배열을 반환하는 메소드들을 활용하면 됩니다.
* splice는 원본데이터를 변경함
setState를 이용할 때 원시타입 경우에는 값을 바로 넣어주어도 되지만
참조타입인 경우에는 새로운 객체나 배열을 생성한 후 값을 넣어주어야 합니다.
// 원시타입
const [number, setNumber] = useState(0)
setState(3)
// 참조타입
const [person, setPerson] = useState({ name: '', age: 30 })
setState({...person, name: 'pyo'})
정리
- 불변성이란 메모리 영역의 값을 변경할 수 없는 것이다.
- 리액트는 불변성을 지켜줌으로써 효율적인 상태업데이트를 한다.
- 리액트는 불변성을 지켜줌으로써 사이드 이펙트를 사전 방지하고 프로그래밍의 구조를 단순하게 유지한다.
- 불변성을 가진 원시타입과 달리 참조타입의 경우에는 의도적으로 불변성을 지켜주어야한다. 이 때 새로운 주소 값을 가진 객체를 생성하여 상태를 업데이트 해준다. spread operator, map, filter, slice, reduce 메소드들을 활용한다.
ref
https://velog.io/@nomadhash/Java-Script-깊은-복사와-얕은-복사
https://curryyou.tistory.com/275
'프론트엔드 > React' 카테고리의 다른 글
리액트 image 어디에다 저장해야할까 (public vs src) (0) | 2022.09.25 |
---|---|
현업에서 바로 적용해보는 프론트엔드 클린코드 (0) | 2021.10.28 |
리액트에서 cryptoJS 사용하는 법 (0) | 2021.04.15 |
간단하지만 명료하게 리액트 의존성 배열 정리 (0) | 2020.10.13 |
리액트 함수 컴포넌트 10가지 반환값 (0) | 2020.09.19 |