blog-imgDucklog

React는 왜 불변성을 지켜야할까?

React는 왜 불변성을 지켜야 하는지를 알아보기 전에 다시 한번 불변성에 대한 개념을 보고 가자!

불변성

저번 불변값 가변값 글에서 불변성은 변하지 않는 바뀔 수 없는 값이라는 것을 알았다.

이 값은 메모리 영역 안에서 변경이 불가능하며 변수에 할당할 때 새로운 값이 만들어진다.


그렇다면 우리는 왜 불변성을 지켜야 할까?

불변성을 지켜야하는 이유

  • 값의 변화를 추적하기 쉬움
  • 원본데이터가 변경되면, 해당 데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생함

불변성을 지키는 방법

1. object 메서드 사용

Object.assign()을 사용하여 데이터를 복사

예시코드

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const answer = Object.assign(target, source);
console.log(answer); // Object { a: 1, b: 4, c: 5 }

Object.freeze()을 사용하여 원시값을 얼림

const obj = {
  prop: 1,
};

Object.freeze(obj);

obj.prop = 3;
// Throws an error in strict mode

console.log(obj.prop);
//  1

Object.seal()를 이용하여 객체를 밀봉함

밀봉하게 되면 객체에 새로운 속성을 추가할 수 없고 삭제할수도 없음.

const object1 = {
  property1: 42,
};

Object.seal(object1);
object1.property1 = 33;
console.log(object1.property1);
// Expected output: 33

delete object1.property1; // Cannot delete when sealed
console.log(object1.property1);
// Expected output: 33

2. 스프레드 문법

  • 스프레드 문법을 이용하여 불변성을 유지할 수 있음
  • 하지만 객체의 깊이가 깊어지면 불변성을 유지하기가 힘들다.
  • why? 만약 중첩된 객체가 있다면 중첩된 모든 객체에 개별적으로 스프레드 연산자를 적용해줘야 하기 때문이다.

const ex = {
  name: 'duck',
  age: 26,
};
const new_ex = { ...ex };
ex.name = 'dev';
console.log(ex.name, new_ex.name); // 'dev' 'duck'

3. immer/immutable 라이브러리 사용

4. map, filter, slice, reduce와 같은 새로운 배열을 반환하는 메서드를 사용


그렇다면 왜 REACT에서 불변성을 지켜야하는가?

React는 상태값을 업데이트 할 때 얕은 비교를 수행한다.

만약 불변성을 지키지 않고 객체나 배열을 직접 변경하게 된다면, Virtual Dom이 Props의 변화를 감지하지 못한다.

React에서 불변성을 지키는 간단한 예시

불변성을 지키지 못한 코드
const [person, setPerson] = useState({
  name: 'duck',
  age: 26,
});

return (
  <div>
    <span>{person.name}</span>
    <span>{person.age}</span>
    <button
      onClick={() => {
        person.age = 25;
        setPerson(person);
        console.log(person.age);
        // 콘솔에는 25 찍히지만 같은 주소를 참조해 리렌더링은 일어나지 않는다.
      }}
    >
      25살로 젊어지게 하는 버튼
    </button>
  </div>
);

불변성을 지킨 코드
const [person, setPerson] = useState({
  name: 'duck',
  age: 26,
});

return (
  <div>
    <span>{person.name}</span>
    <span>{person.age}</span>
    <button
      onClick={() => {
        setPerson((prev) => {
          return { ...prev, age: 25 };
          // 스프레드 연산자를 이용하여 불변성 유지
        });
      }}
    >
      25살로 젊어지게 하는 버튼
    </button>
  </div>
);

리덕스를 사용해본 사람들은 알겠지만,

리덕스를 이용하여 상태관리를 할 때도 불변성을 지켜야한다.

리덕스에서 불변성을 지킨 코드
const initialState = {
   name:'duck',
   job:'front-dev',
   age:null;
}

const reducer = (state=initialState, action) =>{
 switch (action.type) {
    case TEST:
      return {
        ...state, // 스프레드 연산자를 이용한다.
        age:action.data
      };
    default:
      return state
    }
}

하지만 위에서 말한 것처럼 스프레드 연산자를 이용하여 불변성을 지킬 때, 객체의 깊이가 복잡해지면 어렵기 때문에, immer 라이브러리를 이용하자..!