📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄

[React] 내가 이해하기 위해 정리한 리액트의 불변성 개념

불변성(Immutability)이란?

예전에 리액트 관련 글을 쓸 때 분명 불변성이라는 개념을 언급하며 이것에 대해 정리한 적이 있었다. 하지만, 언제 무슨 글에서 썼는지도 모르겠고 불변성이라는 개념은 리액트 문법을 다루기 위해 반드시 알아야 하는 개념이기 때문에 내가 까먹을 때마다 보기 위해서 이번 기회에 제대로 불변성 개념을 정리하는 글을 쓰려고 한다.

 

리액트는 데이터(참조값)가 변했을 때, 리렌더링이라는 것을 한다. 리렌더링 과정에는 업데이트된 부분만 가상 DOM에 렌더링 하여 이전 가상 DOM과 비교하고 달라진 부분만 실제 DOM에 반영시킨다. 즉, 성능 최적화를 위해 전체를 다시 렌더링 하는 게 아닌 달라진 부분만 렌더링 한다. 불변성을 이해하기 위해서는 이와 같은 리액트의 렌더링 방식을 이해하고 있어야 한다.

 

불변성이라는 것은 '메모리 영역에서 값이 변하지 않음'을 의미한다. 즉, 데이터나 상태가 생성된 후에 변경되지 않는다는 개념이다. (한 번 생성된 데이터는 직접적으로 수정하지 않음 === 불변성)

불변성을 지키는 이유는 리액트가 효율적으로 업데이트를 관리하고 컴포넌트의 상태 및 프로퍼티 변화를 추적하기 위함이다. 이전 가상 DOM과 현재 가상 DOM을 비교하여 변경된 부분을 찾아낼 때, 불변성을 유지하면 변경된 데이터를 기존 데이터와 비교할 때 참조값만 비교하면 되기 때문에 효율적으로 변경을 감지할 수 있게 된다.
또한, 데이터 동작의 예측이 가능하며 불필요한 리렌더링을 방지하고 전체적인 애플리케이션의 성능을 향상시킨다.

불변성을 지키는 방법에는 원본 데이터를 직접 수정하는 게 아닌 복사본을 만들어서 새롭게 생성하는 방식을 사용한다. 그때, 주로 스프레드 연산자(...)를 사용하는데, 그 이유는 바로 스프레드 연산자가 깊은 복사 역할을 하기 때문이다. 깊은 복사는 참조값이 서로 다르게 복사하는 것을 말한다. 참조값이 서로 다르면 불변성을 지키는 것이기 때문에 리액트에서는 주로 스프레드 연산자를 사용해서 데이터를 수정한다.

 

우리가 useState()를 사용할 때 객체 혹은 배열을 기존 상태값에서 직접 변경하는 것이 아닌, 복제를 해서 그 복제된 값을 가지고 상태변화함수에 넣어서 사용하는 이유가 바로 이 불변성을 지키기 위함이다. (push를 사용하는 게 아닌 스프레드 연산자를 사용하는 이유이다.)

 

따라서, 리액트에서 상태나 Props를 업데이트할 경우 기존값을 복제하거나 새로운 값을 생성해서 사용하는 것이 중요하다. (한 번 생성된 값은 직접 건드리지 않는다. 건드리고 싶다면 깊은 복사로 복제해서 사용한다.)

 

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

export default const example = () => {
    const [data, setData] = [0, 0, 0]

    // 불변성을 지키지 않음 => 상태값 data의 특정 인덱스 값을 직접 수정하고 있음
    const increaseData = (i) => {
        setData(prevData => {
            data[i]++;
            return prevData;
        })
    }

    // 불변성을 지킴 ① => 기존 상태값을 newData로 복제해서 사용
    const increaseData = (i) => {
        const newData = [...data];
        newData[i]++;
        setData(newData);
    }
    
    // 불변성을 지킴 ② => map 함수를 이용하여 새로운 배열을 반환
    const increaseData = (i) => {
        setData(prevData => {
            return prevData.map((value, index) => (index === i ? value + 1 : value));
        })
    }

    return (
        <div>
            <button onClick={() => increaseData(0)}>data[0]</button>
        </div>
    )
}

불변성을 지키지 않는 경우와 지키는 경우와 차이는 데이터를 직접 업데이트하느냐, 복제해서 업데이트하느냐의 차이이다.

기존 데이터는 스프레드 연산자를 사용해서 복제만 해두고, 더 이상 사용하지 않아야 한다. 복제된 데이터를 가지고 지지고 볶으면 되고 마지막으로 상태변화함수 안에 다가도 복제된 데이터를 넣어주면 된다.

 

리액트의 리렌더링 방식은 '비교'이며 비교를 할 때는 비교의 기준이 될만한 기존값이 있어야 됨을 항상 기억하자.

 

1. 리액트의 리렌더링 방식은 '비교'이며 비교를 할 때는 비교의 기준이 되는 기존값이 존재해야 함.
2. 정확히 말하면 리액트는 기존 가상 DOM과 업데이트된 현재 가상 DOM을 비교하여 다른 부분만 리렌더링함.
3. 기존값을 변하지 않게 하는 것이 바로 '불변성'이며 이것은 리액트 문법의 근간이 되는 개념임.
4. 따라서 상태값이나 Props를 '업데이트' 할 땐, 반드시 기존값을 직접 건드리는 게 아닌 기존값을 '복제'해서 사용해야 함
5. 기존값을 건드리고 싶다면, 깊은 복사로 참조값이 다르게 만들어서 사용하면 된다.