불변성의 중요성

임효진·2023년 3월 20일
0

불변성의 중요성

리액트 컴포넌트에서 상태를 업데이트할 때 불변성을 지키는 것은 매우 중요하다.
useState를 사용해 만든 todos배열과 setTodos함수를 사용하는 onToggle함수를 생성해보겠다.

const [todos, setTodos] = useState(0);
const onToggle = useCallback(id => {
  setTodos(todos=>
  todos.map(todo=>
  todo.id === id ? {...todo, checked: !todo.checked}  : todo,
  ),
  );
},[]);

기존 데이터를 수정할 때 직접 수정하지 않고, 새로운 배열을 만든 다음에 새로운 객체를 만들어서
필요한 부분을 교체해주는 방식이다. 업데이트가 필요한 곳에서는 아예 새로운 배열 혹은 새로운 객체를
만들기 때문에, React.momo를 사용했을 때 props가 바뀌었는지 혹은 바뀌지 않았는지를 알아내서
리렌더링 성능을 최적화해줄 수 있다.

이렇게 기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 '불변성을 지킨다'라고 한다.
다음 예시 코드에서는 불변성을 어떻게 지키고 있는지 생각해보자.

const array = [1, 2, 3, 4, 5];

const nextBadArray = array; //배열을 복사하는 것이 아닌 똑같은 배열을 가리킴.
nextBadArray[0] = 100;
console.log(array === nextBadArray); //완전히 같은 배열이기에 true

const nextGoodArray = [...array]; //배열 내부의 값을 모두 복사함.
nextGoodArray[0] = 100;
console.log(array === nextGoodArray); //다른 배열이기에 false

const object = {
  foo : 'bar',
  value : 1
};

const nextBadObject = object; //객체가 복사되지 않고, 똑같은 객체를 가리킴.
nextBadObject.value = nextBadObject.value + 1;
console.log(object === nextBadObject) //같은 객체이기 때문에 true;

const nextGoodObject = {
  ...object, //기존에 있던 내용을 모두 복사해서 넣는다.
  value : object.value + 1 // 새로운 값을 덮어 쓴다.
}
console.log(object === nextGoodObject) //다른 객체이기 때문에 false

불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못한다.
그러면 React.memo에서 서로 비교하여 최적화하는 것이 불가능하지 않을까?

추가로 스프레드 연산자(...문법)를 사용하여 객체나 배열 내부의 값을 복사할때는 얕은 복사를
하게된다. 즉, 내부의 값이 완전히 새로 복사되는 것이 아니라 가장 바깥쪽에 있는 값만 복사된다.
따라서 내부의 값이 객체 혹은 배열이라면 내부의 값 또한 따로 복사해주어야한다.
다음 코드를 보면 이해가 될 것이다.

const todos = [{id : 1, checked : true}, {id : 2, checked : true}];
const nextTodos = [...todos];

nextTodos[0].checked = false;
console.log(todos[0] === nextTodos[0]); // 현재까지는 똑같은 객체를 가르키고 있기에 true

nextTodos[0] = {
  ...nextTodos[0],
  checked : false
}
console.log(todos[0] === nextTodos[0]) //새로운 객체를 할당해 주었기에 false

만약 객체 안에 있는 객체라면 불변성을 지키면서 새 값을 할당해야 하므로 다음과 같이 해주어야한다.

const nextComplexObject = {
  ...complexObject,
  objectInside : {
    ...complexObject.objectInside,
    enabled : false
  }
};
console.log(complexObject === nextComplexObject) //false;
console.log(complexObject.objectInside === nextComplexObject.objectInside); // false

배열 혹은 객체의 구조가 정말 복잡해진다면 이렇게 불변성을 유지하면서 업데이틑 하는 것도 까다로워진다.
이렇게 복잡한 상황일 경우 immer라는 라이브러리의 도움을 받으면 정말 편하게 작업할 수있다.
예전에 작성한 포스트 중에 immer에 대해 간략하게 설명해놓았으니 참고해보길 바란다.

출처 및 참고 : 리액트를 다루는 기술

profile
핫바리임

0개의 댓글