[React] 스프레드 연산자와 상태 업데이트, 불변성

728x90

[React] 스프레드 연산자와 상태 업데이트, 불변성


 

기존 값을 그냥 단순하게 수정하는 방법으로는 React가 상태가 변경되었음을 감지할 수 없어서 구성 요소를 리랜더링 할 수 없다.

상태 개체에 대한 참조가 변경되었음이 확인이 되어야만 리랜더링을 진행할 수 있는데, 이를 위해 스프레드 연산자를 사용한다.

 

리렌더링 불가능 한 경우

const object = {
  a: 1,
  b: 2
};

object.b = 3;

 

리렌더링 가능한 경우

const object = {
  a: 1,
  b: 2
};

const nextObject = {
  ...object,
  b: 3
};

 

스프레드 연산자는 기존의 state값은 유지시키고 그것의 복사값을 가진 새 개체를 만들어 낸다. 이 때 상태 개체에 대한 새로운 참조가 만들어지므로 React가 변화를 감지하여 리랜더링을 수행할 수 있다.

 

이 방식을 두고 불변성을 유지하면서 업데이트를 한다고 말한다.

불변성 : 어떤 값을 직접적으로 변경하지 않고 새로운 값을 만들어 내는 것.

 

기존 원시 타입에서는 단순하게 대입을 하는 방식으로 수정해도 아무런 문제가 없으나, 객체 타입의 데이터를 어떤 변수에 할당하고 그 변수를 다른 변수에 다시 할당했다면, 배열의 복사가 이루어지는 것이 아니라 같은 참조값을 갖게 된다.

그래서 만약 사본을 수정하는 경우, 원본도 함께 수정이 되어버리는 것이다.

 

 React의 화면 업데이트 과정은 다음과 같다.

1. setState 호출 (혹은 부모로부터 props를 전달 받는다.)
2. shouldComponentUpdate를 실행했는데 fasle를 리턴하면 여기서 멈추고, true를 리턴하면 다음 단계로 이동.
3. 가상 DOM과 실제 DOM을 비교해서 변경사항이 있으면 화면을 다시 그린다.
여기서 shouldComponentUpdate는?
각각의 컴포넌트들은 shouldComponentUpdate라는 메소드를 가지고 있고 이것은 state가 변경되거나 부모 컴포넌트로부터
새로운 props를 전달받을 때 실행됩니다.
React는 이 메소드(shouldComponentUpdate)의 반환 값에 따라서 re-render를 할지에 대한 여부를 결정하게 됩니다.
기본적으로 shouldComponentUpdate 메소드는 true를 반환합니다.
하지만 React 개발자는 re-render를 원하지 않는 경우에, 이 return value를 false로 오버라이드 할 수 있습니다.

불변성이 지켜지지 않으면 객체 내부 값이 바뀌어도 React가 이를 감지할 수 없다.

 

그러나 스프레드 연산자는 얕은 복사를 수행하기 때문에 데이터의 구조가 복잡해지면 불변성을 지켜가면서 새로운 데이터를 생성해내는 코드가 복잡해진다.

 

가령 다음과 같은 객체가 있다고 가정해보자.

const state = {
  posts: [
    {
      id: 1,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 1,
          text: '와 정말 잘 읽었습니다.'
        }
      ]
    },
    {
      id: 2,
      title: '제목입니다.',
      body: '내용입니다.',
      comments: [
        {
          id: 2,
          text: '또 다른 댓글 어쩌고 저쩌고'
        }
      ]
    }
  ],
  selectedId: 1
};

여기서 posts 배열 안의 id 가 1 인 post 객체를 찾아서, comments 에 새로운 댓글 객체를 추가해줘야 한다고 가정하자.

그럼 아래와 같이 업데이트가 이루어져야 한다.

const nextState = {
  ...state,
  posts: state.posts.map(post =>
    post.id === 1
      ? {
          ...post,
          comments: post.comments.concat({
            id: 3,
            text: '새로운 댓글'
          })
        }
      : post
  )
};

 

immer 라이브러리는 데이터가 복잡한 구조로 이루어졌을 때 위의 방식보다 더 쉽게 데이터를 업데이트할 수 있는 방법을 제공한다.

immer는 개발자가 상태를 업데이트 할 때, 불변성을 신경쓰지 않아도 알아서 대신 해주기 때문에 굉장히 편리하게 구현할 수 있도록 도와준다.

아래는 위의 코드를 immer 라이브러리를 활용하여 구현한 방법이다.

const nextState = produce(state, draft => {
  const post = draft.posts.find(post => post.id === 1);
  post.comments.push({
    id: 3,
    text: '와 정말 쉽다!'
  });
});
728x90