React
에서 배열이나 객체를 업데이트 해야 할 때에는 직접 수정하면 안되며, 반드시 불변성을 지켜주면서 업데이트를 해야 한다.
불변성이 프로그래밍에서 주목받는 이유는 변경 가능한 상태를 여러 곳에서 공유하게 됨으로써 발생하는 여러 가지 문제를 해결하기 위함이었다.
이런 문제는 멀티 스레딩과 같은 동시성 프로그래밍(동시에 값을 읽고 다른 동작을 할 때)을 사용할 때 많이 발생했는데 기존에는 락을 걸어놓고 락이 풀린 상태에만 스레드가 상태에 접근할 수 있도록 허가하는 방식을 주로 사용했었다.
즉, 불변성(Immutability
)은 상태나 값을 변경하지 않는 것이다.
JavaScript
의 경우 원시 타입의 값들은 불변성을 갖고 있다.
let a = 0
a = 1
console.log(a);
// 결과 : 1
위의 코드처럼 변수에 값을 재할당하면 a
가 참조하고 있는 메모리의 값이 변경되는 것이 아니라 새로운 값인 2
가 메모리에 새로 저장되고 그 주소 값이 a
에 할당되는 방식으로 작동한다.
하지만, 원시 타입 값이 아닌 다른 참조형 값들(객체, 배열, 함수 등)은 다른 방식으로 작동한다.
const object = { a: 0 };
object.a = 1;
console.log(object);
// 결과 : { a: 1 }
위의 코드에서 object
의 내부 프로퍼티의 값을 변경했을 때 object
에서 참조하고 있는 a
값은 변경되지 않고 값이 변경된다.
단, a
가 참조하고 있는 1
의 주소 값은 변경된다.
이처럼 JavaScript
객체는 가변한며, React
에서는 JavaScript
객체가 가변하기 때문에 문제가 된다.
React
의 상태값 불변성의 필요React
에서 상태값의 불변성을 유지해야 하는 이유는
값이 변경됐는지 알기 위함
값을 수정하면, 이전 상태와 바뀐 상태값이 동일해져서 비교할 수 없음
등이 있다.
React
에서는 상태가 변하면 컴포넌트가 리렌더링이 된다.
React
에서 리렌더링할 때 값 자체가 아니라 참조 값을 비교하므로 참조 값이 동일하면, 변경을 감지할 수 없게 된다.
또한, 값을 수정하면 이전 상태와 바뀐 상태값이 동일해지기 때문에, 이전 상태와 현재 상태를 비교하여 렌더링할 수 없게 된다.
불변 객체를 사용하지 않고 객체의 변화를 감지할 수 있지만, 효과적으로 객체의 변화를 감지할 수 있는 가장 좋은 방법은 불변 객체를 사용하는 것이다.
JavaScript
에서 객체를 불변하게 만드는 방법은 크게 3가지 정도가 있다.
assign 메서드 사용
spread 연산자 사용
immer
라이브러리 사용
MobX
라이브러리를 통한 전역 상태 관리
등이 있다.
Object.assign()
Object.assign
메서드를 통해 불변성을 유지할 수 있다.
const object = { a: 0 };
object.a = 1;
console.log(object);
// 결과 : { a: 1 }
const cloneAssign = Object.assign({}, object, { b: 2 });
console.log(cloneAssign);
// 결과 : { a: 1, b: 2 }
Object.assign()
은 매개 변수로 전달된 객체의 모든 속성을 첫 번째 매개 변수에 지정된 객체에 복사한다.
주의 사항으로는 Object.assign()
은 속성의 값을 복사(얕은 복사)하기 때문에, 깊은 복사를 수행하려면 다른 방법을 사용해야 한다.
Spread
연산자Spread
연산자를 이용하면, 객체 혹은 배열을 펼칠 수 있으며, 객체나 배열을 통채로 끌고와서 사용이 가능하다.
또한 기존의 것은 건들이지 않고 새로운 객체를 만들 때 사용한다. (기존 값의 불변성 유지)
const object = { a: 0 };
object.a = 1;
console.log(object);
// 결과 : { a: 1 }
const cloneAssign = Object.assign({}, object, { b: 2 });
console.log(cloneAssign);
// 결과 : { a: 1, b: 2 }
const cloneSpread = { ...object, ...cloneAssign, c: 3 };
console.log(cloneSpread);
// 결과 : { a: 1, b: 2, c: 3 }
const numberObject = { one: '하나', two: '둘', three: '셋' }
const { three, ...rest } = numberObject;
console.log(three);
// 결과 : 셋
console.log(rest);
// 결과 : { one: '하나', two: '둘' } - rest 안에는 three 값을 제외한 값이 들어있음
console.log(numberObject);
// 결과 : { one: '하나', two: '둘', three: '셋' }
immer
라이브러리 사용위의 두 가지 방식을 사용하면 얕은 복사가 된다.
그러므로 객체 안의 객체가 있으면 그 안의 객체도 복사해 주어야 한다.
하지만 무분별하게 깊은 복사를 사용하는 것은 다음과 같은 이유로 문제가 될 수 있다.
깊은 복사는 성능이 저하될 수 있다.
리액트에서 변경되지 않은 객체도 변경되었다고 감지하여 모든 것을 리렌더링할 수 있다.
그러므로, 변경된 객체만 변경해주는 것이 좋다.
immer
와 같은 라이브러리를 사용하여 쉽게 불변성을 관리할 수 있다.
Immer performance 문서를 보면, 변경된 객체만 복사하여 성능을 향상시킬 수 있다고 나와 있다.
위의 3가지의 불변성을 유지한 프로그래밍의 경우 객체 지향 프로그래밍에 적합하지 않으며, 불변성 유지를 위해 계속해서 노력하고 신경을 써야한다.
MobX
라이브러리를 통한 전역 상태 관리
MobX
는 전역 상태 라이브러리로, 모든 상태 변화롤 일어나는 부분으로 자동으로 추적해주는 역할을 한다.
MobX
는 다음과 같은 특징을 가지고 있다.
React
에 종속적인 라이브러리가 아님
아키텍처나 상태 컨테이너가 아닌 라이브러리
Redux
와 다르게 store
에 제한이 없음
또한 Redux
에서 해줘야했던 action
선언, connect
, mapStateToProps
, mapDispatchToProps
등 번거로운 작업들은 데코레이터로 간단하게 대체
observable
을 기본적으로 사용하고 있음
Mobx
는 절대적으로 필요한 경우에만 state
변경
Typescript
를 기반으로 만들어짐
객체 지향 프로그래밍에 적합
불변성을 신경쓰지 않아도 내부적으로 처리
후에 추가적으로 MobX
라이브러리에 대해 사용 및 정보 등을 포스팅할 예정이다.
참고 사이트
구보현 블로그 - 리액트의 상태가 불변해야 하는 이유
badahertz52.log - React의 불변성
MDN - Object.assign()
Spread / React 문법 (feat. 구조 분해 할당)
Immer - Introduction to Immer
기억보다 기록을 - mobx 알아보기