React의 setState가 잘못된 값을 주는 이유

Myungho·2020년 4월 9일
16

본 글은 React의 공식 홈페이지에서 state에 대해 설명한 부분을 참고하여 작성되었습니다.
더 자세하게 알고싶으신 분은 공식 홈페이지를 참고해주세요.

목차

  • 서론
  • setState가 잘못된 값을 주는 이유
  • setState는 언제 비동기일까?
  • state의 값을 가장 최신으로 유지하는 방법
  • state를 동기적으로 업데이트하지 않는 이유

서론

React는 DOM을 직접 조작하는 대신 Virtual DOM을 이용해 변경사항을 조작하므로 렌더링 성능이 우수하다고 알려져있습니다. 하지만 변경사항을 조작하는 것 자체가 느리다면 전체적으로 성능이 약화될 수 밖에 없을 것입니다. React는 컴포넌트의 state가 변경되면 렌더링을 다시하게 되는데, state의 변경이 한꺼번에 여러번 많이 발생하게 된다면 그 만큼 많은 렌더링이 많이 발생하게 될 것입니다. 이를 방지하기 위해 state의 변경사항을 즉시 반영하지 않고 변경사항을 대기열에 넣어 한꺼번에 적용시킵니다. 이 과정에서 setState 호출 후의 state 값이 개발자가 의도하지 않는 값이 될 수도 있습니다.

setState가 잘못된 값을 주는 이유

React에서 this.propsthis.state는 모두 렌더링된 값을 나타냅니다. 즉, 렌더링이 된 직후 현재 화면에 보이는 값이 저장되어 있는 것입니다. 하나의 setState 호출이 한 번의 컴포넌트 렌더링에 해당할까요? 아래의 예시를 살펴보죠.

import React, { Component } from 'react';

class App extends Component {
  state = {
    count: 0
  }

  add() {
    this.setState({count: this.state.count+1});
  }
  
  handleClick() {
    this.add();
    this.add();
    this.add();
  }
  
  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={() => this.handleClick()}>Add</button>
      </div>
    );
  }
}

export default App;

이 코드의 의도는 this.state.count의 값을 3씩 증가시켜주는 것입니다. 하지만 실제로 위 코드는 3이 아닌 1씩 증가하게 됩니다. 왜 이런 현상이 발생하는 것일까요?

이것은 setState의 호출이 비동기적으로 이뤄지기 때문입니다. setState는 컴포넌트를 즉각적으로 갱신하지 않고 변경사항을 대기열에 집어넣어 여러 변경사항과 함께 일괄적으로 갱신합니다. 이를 위해 컴포넌트의 렌더링을 뒤로 미룰수도 있는 것이죠. 즉, setState를 여러번 호출한다고 해도 한 번의 렌더링만 이루어질 수 있고, 따라서this.statesetState에 의해 여러번 조작되는 동안 변경되지 않고 단 하나의 값만 가지게 됩니다.

setState는 언제 비동기일까?

setState는 이벤트 핸들러 내에서 비동기적으로 동작합니다.

즉, 하나의 이벤트 핸들러 내에서 setState가 여러번 호출된다면, 이벤트가 끝날 시점에 state를 일괄적으로 업데이트하고 렌더링합니다. 이는 더 큰 규모의 앱에서 뚜렷한 성능 향상을 만들어냅니다.

state의 값을 가장 최신으로 유지하는 방법

가장 좋은 방법은 setState를 호출하자마자 this.state에 접근하지 않는 것입니다. 하지만 상황이 여의치 않다면 componentDidUpdate 사용, setState Callback 사용 또는 setState의 updater 인자에 함수 사용하는 방법을 이용할 수 있습니다. 위 세가지 방법 모두 갱신이 이루어진 후에 실행되는 것이 보장되는 방법입니다.

add() {
  this.setState({count: this.state.count+1});
  console.log(this.state.count); // 갱신 전의 값이 출력
}

handleClick() {
  this.add();
}

이 코드는 개발자가 의도한 대로 동작하지 않습니다. this.state가 즉각 반영되지 않기 때문입니다. 아래의 세 가지 방법을 통해 위 문제를 해결할 수 있습니다.

// 1. componentDidUpdate 사용
componentDidUpdate() {
  console.log(this.state.count);
}

add() {
  this.setState({count: this.state.count+1});
}

handleClick() {
  this.add();
}

// 2. setState Callback 사용
add() {
  this.setState({count: this.state.count+1}, () => {
    console.log(this.state.count);
  });
}

handleClick() {
  this.add();
}

// 3. setState의 updater 인자에 함수 사용
// count의 값이 3씩 증가한다.
add() {
  this.setState((state, props) => {
    return {count: state.count + 1}
  });
}

handleClick() {
  this.add();
  this.add();
  this.add();
}

3번째 방법인 setState의 updater 인자에 함수를 사용하는 경우, 함수 내부에서 state의 값을 직접 조작해서는 안됩니다. 대신 인자로 전달된 stateprops를 기반으로 새로운 객체를 만들어 변경사항을 반환해야 합니다.

state를 동기적으로 업데이트하지 않는 이유

이전에 설명했듯이 모든 컴포넌트가 자신의 이벤트 핸들러 내에서 setState를 호출할 때까지 React는 컴포넌트를 렌더링하지 않고 내부적으로 기다리고 있습니다. 이를 통해 불필요한 렌더링을 방지하면서 성능을 향샹시킵니다. 또, state를 동기적으로 업데이트하는 것은 propsstate 사이의 일관성을 해칠 수 있고, 디버깅이 매우 어려운 이슈를 일으킬 수 있기 때문입니다.

더 자세한 예시와 설명은 이곳에서 확인할 수 있습니다.

profile
자바스크립트로 개발하는 새내기입니다.

1개의 댓글

comment-user-thumbnail
2023년 6월 20일

좋은글 감사합니다! 이해가 됐어요!

답글 달기