React에서 불변성을 지켜야하고 어떻게 지켜야하는지는 알고있었지만 왜?? 지키는지는 생각을 해보지는 않아서 작성을 해봅니다.
불변성을 검색해보면 불변성이란 값이나 상태를 변경할 수 없는 것을 의미합니다.
이런 검색결과가 많이 나온다,
솔직히 저 문구로만은 불변성이란 이 친구를 이해하기는 어려운점이 있다.
그래서 자바스크립트에 원시타입 객체타입을 기반으로 정리를 해보자면,
Boolean, Number, String, null, undefined, Symbol
과 같은 타입들은 불변성을 유지하는 타입들이고 Object
타입들은 변경가능한 값들입니다.
let string = 'data1'
string = 'data2'
변수 string은 data1 -> data2로 값이 변경되었습니다.
위 예시를 보면 string 변수는 'data1' → 'data2' 로 값이 변경된 것처럼 보입니다.
하지만 실제 메모리영역에는 'data1', 'data2' 둘다 존재합니다.
let string = 'data1' // 1. string: 'data1'가 메모리 영역1에 등록됩니다.
string = 'data2' // 2. string: 'data2'가 메모리 영역2에 등록됩니다.
위 예시에서 메모리 영역을 총 2개 사용한 것입니다.
변수 string은 'data1' 였고, 여기에 'data2'를 재할당하였는데 기존 메모리 영역 1에 있는 'data1'의 값은 그대로 두고, 메모리 영역2에 'data2'를 새로 할당했습니다.
즉, 메모리영역에서 'data2'는 'data1'을 대체하는 것이 아니라 새로운 영역에 할당됩니다.
그로써 아래 예제도 똑같은
let a = 10;
let b = a;
a = 20;
console.log(a,b); //20 10
자바스크립트에서 Number값은 불변성을 유지하기 때문에 새롭게 20이라는 값을 가지는 주소를 a에 할당하게 되기 때문에 위와 같은 결과가 나오게 됩니다.
다른 원시타입들도 마찬가지이고, 이걸 불변성이라고 합니다
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]을 할당하고 새로운 참조값을 만들어주어 불변성을 지켜준 것이된다.
또한
var coke = {
name: 'coca',
price: 2980,
}
var new_coke = coke;
coke.name = 'pepsi';
console.log(coke.name,new_coke.name);//'pepsi' 'pepsi'
원본을 메모리영역은 그대로이고 그 안에 값을 변경한 것이여서 이런 결과들이 출력된다 (불변성x)
"불변성의 진짜 의미는 메모리 영역에서 값을 변경할 수 없다는 의미입니다."
그럼 이제 React에서 불변성을 지키는 이유를 보면 리액트에서 상태 업데이트를 하는 원리 때문입니다
리액트는 상태값을 업데이트 할 때 얕은 비교를 수행합니다. 즉 객체의 속성 하나하나를 비교하는게 아니라 참조값만 비교하여 상태 변화를 감지합니다.
따라서 Object or Array의 속성이나 값을 바꾸는 불변성이 없는 변경은 상태가 바뀌었다고 React에서 파악을 못하기 때문에 이런 불변성을 지키는 업데이트가 필요합니다
그러므로 배열이나 객체를 새로 생성해서 새로운 참조값을 만들어서 상태를 업데이트 합니다.
이렇게 불변성을 지켜줌으로써 또 다른 이점은 바로 사이드 이펙트를 방지하는 것입니다. 즉 외부에 존재하는 원본데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용하기에 예상치 못한 오류를 사전에 방지할 수 있습니다. 다시 반대로 생각해보면 외부의 값을 함부로 변경할 수 있는 것은 위험한 일입니다. 만약 다른 어떤 곳에서 원본데이터를 사용하고 있다고 하면 어플리케이션 어딘가에서 사이드 이펙트가 일어날 가능성이 있기 때문입니다.
React 에서는 얕은 비교를 통해 새로운 값인지 아닌지를 판단 한 후, 만약 새로운 값임을 판단하게 되면 부모 컴포넌트가 리렌더링을 하면 자식 컴포넌트도 함께 리렌더링 되게 된다.
객체 경우에는
const myObj = {
a: 1
}
myObj.a = 2
🍀이러한 이유 때문에 state 값을 변경하고 React에게 리렌더링을 원한다고 알리고 싶다면 새로운 배열을 생성해서 새로운 참조값을 생성하고 그 안에 기존의 값을 넣어줘야 한다!🍀
spread operator, map, filter, slice, reduce 등등 새로운 배열을 반환하는 메소드들을 활용하면 됩니다.
setState를 이용할 때 원시타입 경우에는 값을 바로 넣어주어도 되지만
참조타입인 경우에는 새로운 객체나 배열을 생성한 후 값을 넣어주어야 합니다.
주로 자바스크립트에서 객체를 불변하게 만드는 방법은 크게 3가지 정도가 있다. assign 메서드 사용, spread 연산자 사용, 라이브러리 사용이다.
Object.assign 메서드를 사용할 수 있다.
Object.assign({}, myObj, {
b: 2
})
Object.assign은 매개 변수로 전달된 객체의 모든 속성을 첫 번째 매개 변수에 지정된 객체에 복사한다.
var coke = {
name: 'coca',
price: 2980,
}
var new_coke = {...coke};
coke.name = 'pepsi';
console.log(coke.name,new_coke.name);//'pepsi' 'coca'
스프레드 문법을 사용하여 객체를 복사해야지 객체가 불변성을 유지할 수 있습니다.
하지만 스프레드 문법은 1레벨 깊이에서만 유효하게 동작하기 때문에 객체 내부의 객체의 불변성까지는 유지할수 없습니다.
var coke = {
name: 'coca',
fake: {
name: 'pepsi',
}
}
var new_coke = {...coke};
coke.fake.name = 'coca zero';
console.log(coke.fake.name,new_coke.fake.name);
//'coca zero' 'coca zero'
만약 레벨2 객체까지 불변성을 유지해주려면 아래와 같이 별도의 변수에 값을 재할당하고 넣어주는 번거러운 과정을 거쳐야 합니다.
const coke = {
name: 'coca',
fake: {
name: 'pepsi',
}
}
const new_fake = {...coke.fake};
const new_coke = {...coke};
new_coke.fake = new_fake;
coke.fake.name = 'coca zero';
console.log(coke.fake.name,new_coke.fake.name);
//'coca zero' 'pepsi'