본문 바로가기
React

[리액트] 리액트에서 state를 직접 수정하면 안되는 이유 (데이터의 불변성)

by 메이플 🍁 2022. 4. 8.

state란?

state는 컴포넌트 내부적으로 사용되는 값을 말하며 state 값이 업데이트 될때 리액트 전체가 리렌더링이 된다.

 

리액트에서 state를 직접 수정하는 예제 (잘못된 방법)

import React from "react";

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      animals: ["cat", "elephant", "dolphin"]
    };
  }

  // setState()함수 안에서 기존 animals 배열에 "dog"를 직접 추가했다
  onClick = () => {
    this.setState({
      animals: ["cat", "elephant", "dolphin", "dog"]
    })
  };

  render() {
    return (
      <>
        <ul>
          {this.state.animals.map((animal, index) => (
            <li key={index}>{animal}</li>
          ))}
        </ul>
        <button onClick={this.onClick}>Add dogs</button>
      </>
    );
  }
}

export default App;

 

불변성이란?

리액트에서 어떤 값을 업데이트할때는 불변성(immutable)을 지켜주면서 업데이트를 해야한다. 사전적 의미의 불변성이란 값이나 상태를 변경할 수 없는 것을 말한다.

 

원시타입 vs 참조타입

[자바스크립트] 자바스크립트의 데이터 종류 6가지 (원시타입과 참조타입)

원시타입 (Primitive Type)

  • 더 이상 작은 단위로 나눠질 수 없는 싱글 타입
  • 값(value) 자체가 메모리에 저장
  • 종류: string, number, boolean, null, undefined, symbol

원시타입으로 불변성 유지

원시타입에서 재할당이 일어날 경우에는 메모리에 있던 기존 데이터를 새로운 데이터로 교체하는 것이 아닌 기존의 데이터를 그대로 두고 새로운 메모리에 새로운 데이터를 넣은 후 변수의 포인터만 새로운 메모리를 가리키게된다. 즉 아래의 예제에서 실제 메모리영역에는 'data1', 'data2' 둘다 존재하므로 원시타입으로 불변성을 지켜주었다.

let string = 'data1' // 메모리1에 data1이 저장
string = 'data2' // 메모리2에 data2가 저장

// string이라는 변수에 재할당이 일어났을때 데이터가 저장된 메모리를 가리키는 포인터만 변경되었다!!

참조타입 (Reference Type, Objective Type)

  • 여러개의 싱글 타입을 묶어서 한 단위로 관리
  • 값(value)이 레퍼런스 넘버에 저장
  • array, object, function

참조타입으로 불변성 유지

예제에서 array.push(5)는 원본 배열을 수정하면서 불변성을 지키지 않고 있고 array = [1, 2, 3, 4]는 원본 배열을 수정하는게 아니라 새 참조값을 가진 새로운 배열 [1, 2, 3, 4]을 할당하여 불변성을 지켜주고 있다.

let array = [1, 2, 3, 4] // 메모리영역 1 
array.push(5) // 메모리영역 1
// → 메모리영역 1에 있던 기존 데이터가 수정되었으므로 불변성을 지키지않음

array = [1, 2, 3, 4] // 메모리영역 2 (새로운 참조값)
// → 메모리영역 1에 있던 기존 데이터가 수정되지 않고 새로운 메모리에 새로운 데이터가 들어갔으므로 불변성을 지킴

 

불변성의 진짜 의미는 기존 데이터가 수정되지 않고 새로운 메모리에 새로운 데이터를 올려주는 것을 말한다

 

리액트에서 어떤 값을 업데이트하기 위해서는 상태 변경을 하지 않아야 한다는 것이다. 상태를 변경하지 않고 어떻게 업데이트를 할 수 있을까? 정답은 업데이트를 할때 기존의 값과 새로운 값을 가지고 있는 데이터를 재선언해 다시 메모리에 올리는 것이다.

 

리액트에서는 왜 불변성을 지켜줘야할까?

리액트는 얕은 비교를 통해 새로운 값인지 아닌지를 판단한 후 새로운 값인 경우 리렌더링을 한다. 여기서 얕은 비교란 객체, 배열과 같은 참조 타입을 실제 내부 값까지 비교하지 않고 동일 참조인지(동일한 메모리 값을 사용하는지)를 비교하는 것을 뜻한다. 즉 리액트는 객체, 배열과 같은 참조 타입의 내부 값이 바뀌어도 알 수가 없으니 리렌더링이 발생하지 않는다. 리액트가 객체, 배열의 값이 업데이트 되었다는것을 알려면 메모리에 다시 올라가야한다. 객체, 배열의 데이터가 메모리에 다시 올라가기 위해서는 내부의 값을 수정하는 것이 아닌 다시 선언해야한다.

정리

  1. 리액트는 얕은 비교를 통해 새로운 값인지 아닌지 판단한다 이때 새로운 값인 경우에는 리렌더링을 한다
  2. 여기서 얕은 비교란 참조 타입 데이터의 내부 값까지 비교하지 않고 동일 참조인지 (동일한 메모리 값을 사용하는지) 비교하는 것을 말한다
  3. 리액트는 객체, 배열과 같은 참조타입의 내부 값이 바뀌어도 알수가 없으니 리렌더링이 발생하지 않는다
  4. 리액트에서 객체, 배열의 값이 업데이트 되었다는 것을 알리려면 메모리에 다시 올라가야한다
  5. 즉 리액트에서 객체, 배열의 값이 업데이트 되었다는 것을 알리려면 내부의 값을 수정하는 것이 아닌 다시 선언해 메모리에 올려야한다

 

리액트에서 참조데이터를 가진 state를 직접 수정하면 안되는 이유

state도 객체, 배열과 같이 얕은 비교를 통해 컴포넌트가 리렌더링 된다. 즉 리액트는 state의 내부 값이 직접 바뀐다면 알 수가 없다는 의미이다. state를 바꾸고 돔을 다시 만들려면, 새로운 객체 or 배열을 만들어 새로운 참조값을 만들고, react에게 이 값은 이전과 다른 참조값임을 알려야한다.

 

리액트에서 state를 올바르게 수정하는 방법

리액트에서 기존 코드를 변화시키지 않는 배열의 메서드를 사용해 state값의 불변성을 지켜주면서 업데이트 해야한다.

  • 데이터를 추가할때: concat, spread operator
  • 데이터를 수정할때: map
  • 데이터를 삭제할때: filter

메서드 사용법을 더 자세히 알고 싶다면 👉  [리액트] 리액트에서 자주 쓰이는 자바스크립트 배열의 메서드 3가지 (concat, filter, map)

 

리액트에서 state를 직접 수정하는 예제 (올바른 방법)

import React from "react";

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      animals: ["cat", "elephant", "dolphin"]
    };
  }

  // 데이터를 추가할때 state의 값을 직접 수정하지 않고 concat이라는 메서드를 사용해 불변성을 유지시켜준다
  onClick = () => {
    this.setState({
      animals: this.state.animals.concat(["dog"])
    });
  };

  render() {
    return (
      <>
        <ul>
          {this.state.animals.map((animal, index) => (
            <li key={index}>{animal}</li>
          ))}
        </ul>
        <button onClick={this.onClick}>Add dogs</button>
      </>
    );
  }
}

export default App;

 

정리

  • 리액트에서 상태변화를 감지하려면 이전 state에서 참조하고 있던 메모리 주소와 지금 메모리 주소를 비교해서 메모리 주소가 바뀌면은 상태가 바뀌었다고 인지한다 (얕은비교)
  • 리액트가 참조형 데이터의 변화를 감지하기 위해서는 메모리 주소가 바뀌어야하므로 참조형 데이터의 state를 업데이트할때 불변성을 지켜준다
  • 여기서 불변성이란 기존 데이터를 변경하지 않고 새로운 데이터를 새 메모리에 올리는 것을 말한다
  • state를 업데이트할때 불변성을 지켜야 리액트에서 상태가 업데이트되었음을 감지할 수 있고 이에 따라 필요한 리렌더링이 일어난다
  • 기존 state를 직접 수정하게 되면 리액트가 state값이 업데이트 되었는지 감지하지 못해 컴포넌트에서 리렌더링이 일어나지 않는다

댓글