관리 메뉴

개발 여행자, 현

[React] state 변경함수 사용시 유의할 점 (setState 동기처리) 본문

React

[React] state 변경함수 사용시 유의할 점 (setState 동기처리)

예스현 2022. 11. 15. 01:10

[React] state 변경함수 사용시 유의할 점 (setState 비동기처리) 

 


 

동기(synchronous) / 비동기처리(asynchronous)란?

자바스크립트는 일반적인 코드를 작성하면 동기적으로 처리한다.

동기적으로 처리한다는 것을 쉽게 얘기하면 코드를 적은 순서대로 윗줄부터 차례로 코드가 실행된다는 뜻이다.

사실 거의 모든 프로그래밍 언어들은 위에서 부터 차례대로 동기적으로 코드가 실행된다.

 

console.log(1+1)
console.log(1-1)
console.log(1)

// 결과값
2
0
1

 

 

자바스크립트에서 비동기 처리를 이용하고자 하면 ajax, setTimeout과 같은 함수들을 사용한다.

이런 함수들은 처리시간이 오래걸리는데, ajax를 예로 들면 인터넷 상황이 안좋으면 코드 실행이 오래걸려 10초 이상이 걸릴 수도 있다.

 

만약 동기적으로 코드가 실행된다면, 사용자 입장에서는 10초동안 아무런 화면 페이지를 보지못하고 하염없이 기다려야 한다.

그래서 ajax 요청하는 코드들은 순차적으로 실행되지 않고 비동기적으로 처리된다.

 

비동기란 예를들면 다음과 같다.

console.log(1)
setTimeOut(()=>{console.log(2)},2000)
console.log(3)

// 결과값
1
3
2

1과 3이 출력되고 그 다음에 2가 출력된다.

2를 출력하는 코드가 비동기 처리를 지원하는 코드여서 그렇다.

2를 출력할 때 오래걸리면 완료될 때까지 잠깐 보류했다가, 다른 코드를 먼저 실행한다는 의미이다.

 

심지어 ajax요청이 0초 걸려도 1, 3이 먼저, 그 다음 2가 출력된다.

(물리적으로 잠깐 처리가 보류되어서 그렇다고 한다)

 

setState의 비동기 처리방식

버튼을 클릭하면 숫자를 1 더하고 빼는 간단한 컴포넌트를 만들었다.

alertNumber()라는 함수를 만들어 현재 숫자가 무엇인지 알려주는 창을 띄어준다.

 

import React, { Component } from "react";

class Counter extends Component {
  state = {
    number: 0,
  };

  alertNumber() {
    alert(this.state.number);
  }
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
            console.log(this.state);
            this.alertNumber();
          }}
        >
          +1
        </button>
        <button
          onClick={() => {
            this.setState({ number: number - 1 });
            console.log(this.state);
            this.alertNumber();
          }}
        >
          -1
        </button>
      </div>
    );
  }
}

export default Counter;

 

리액트는 상태 값이 바뀌면 렌더링이 된다.

그렇다면 setState가 컴포넌트에서 여러 개 사용될 때에는 사용된 만큼 화면이 렌더링 되어야한다.

만약 100개 setState 함수가 있다면, 한 순간에 100번의 렌더링이 이루어질 것이다.

 

이러한 문제로 인해 리액트에서는 객체의 값이 변할 때 해당 컴포넌트에서 변한 state 값을 모두 취합한 후

한 번에 렌더링하도록 한다. 이렇게 하면 100개의 값이 한 번의 렌더링으로 모두 갱신될 수 있다.

 

해결법 (콜백함수 이용)

import React, { Component } from "react";

class Counter extends Component {
  state = {
    number: 0,
  };

  alertNumber() {
    alert(this.state.number);
  }
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 }, () => { //변경된 부분
              console.log(this.state);
              this.alertNumber();
            });
          }}
        >
          +1
        </button>
        <button
          onClick={() => {
            this.setState({ number: number - 1 }, () => { //변경된 부분2
              console.log(this.state);
              this.alertNumber();
            });
          }}
        >
          -1
        </button>
      </div>
    );
  }
}

export default Counter;

콜백 함수를 이용해 해결했다.

 

위에선 this.setState와 console.log, this.alertNumber()의 연결고리를 만들어 주지 않았는데

이번에는 콜백 함수 ( ()=>{} )로 연결고리를 만들어 주었다.

 

예시로 알아보는 또 다른 해결법 (useEffect)

예를들어

(1) 버튼을 클릭했을 때 count라는 state 값이 +1 (버튼누른 횟수 기록용)

(2) 버튼을 클릭했을 때 age라는 state 값이 +1

(3) count state값이 3 이상이면 더 이상 age라는 state에 +1을 하지않음

다음과 같은 코드를 짜면

function App(){
  let [count, setCount] = useState(0);
  let [age, setAge] = useState(20);

  return (
    <div>
      <div>나이 {age}</div>
      <button onClick={()=>{
  	setCount(count+1);
  	if ( count < 3 ) {setAge(age+1);}
	}}>클릭</button> 
    </div>
  )

}

그러면 아마 count라는게 2일 때 까지 실행해주니까

age는 20에서 22가 되면 더이상 증가하지 않고 멈출 것이라고 예상할 수 있다.

근데 23까지 증가하는 문제가 발생했다.

 

1) App 컴포넌트안에 useEffect를 만든다.

<button onClick={()=>{
  setCount(count+1);
}}>한 살</button>

우선 버튼 클릭 함수를 다음과 같이 변경한다.

useEffect(()=>{
  if ( count != 0 && count < 3 ) {
    setAge(age+1)
  }
 }, [count])

useEffect는 컴포넌트가 렌더링/재렌더링될 때 실행되는 함수이다.

근데 뒤에다가 [] 대괄호안에 state를 집어넣으면

state가 변경될 때 실행됩니다.

그래서 이를 이용하면 비동기로 인해 발생하는 문제를 해결할 수 있다.

1. count라는 state 값이 변경되면 2. age값도 변경한다.

이런 식으로 순차적으로 코드를 실행할 수 있는 것이다.

 

다만 주의할 점은, useEffect는 페이지 처음 로드될 때도 한번 실행이 되기 때문에 조건문을 이용해 분기처리를 해주어야한다.

이 코드에서는 count가 0일 때 내부 코드가 실행되지 않도록 했다.

 

또 다른 해결법으로는

- count와 age를 동시에 한 곳의 state에 array/object 자료형으로 넣는 것도 하나의 방법이 될 수 있을 것 같고

- Mobx로 컴포넌트의 state 값을 관리하는 것도 하나의 방법이 될 것 같다. Mobx를 사용하면 기존 setState가 비동기로 동작해서 발생하는 이슈가 발생하지 않는다. Mobx의 상태 변화 메커니즘은 함수 호출 즉시 반영되며, setState와는 다르게 불변성 유지를 신경 쓰지 않아도 되기에 컴포넌트의 코드가 매우 간결해진다. 다음엔 Mobx에 대해 알아보고 프로젝트에 적용해보려고 한다.

 

 

참고 레퍼런스

https://codingapple.com/unit/react-setstate-async-problems/

 

state 변경함수 사용할 때 주의점 : async - 코딩애플 온라인 강좌

(짧아서 글로 진행합니다) 자바스크립트의 sync / async 관련 상식 자바스크립트는 일반적인 코드를 작성하면 synchronous 하게 처리됩니다. 번역하면 동기방식 이런데..  뭔소리냐면 코드 적은 순서

codingapple.com

https://simsimjae.tistory.com/448

 

내가 React의 setState를 안쓰는 3가지 이유(Mobx로 local state 관리하기)

blog.cloudboost.io/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e 3 Reasons why I stopped using React.setState Since a few months I’ve stopped using React’s setState on all my new React components. Don’t get me wrong, I didn’t stop havin

simsimjae.tistory.com