프론트 엔드/React

[React]setState는 비동기 동작

Koras02 2021. 10. 12. 19:13

React를 통해서 웹 개발을 하다보면 당연스럽게 만지게 되는 주요 메소드들이 있는데 그 중에 하나가 setState입니다.

 

1. setState는 어느 기능을 가지고 있는가?

리액트는 state가 변화될때 다시 render를 해주기 때문에 setState는 component를 re-render할때 꼭 필요합니다.

그런데 setState를 사용하다보면 state가 정상적으로 업데이트도지 않거나 조금 느린 템포로 업데이트되는 상황을

종종 찾아볼 수 있습니다.

 

그럼 이제 왜 그런 상황이 발생하는지 좀더 자세히 알아보도록 해보겠습니다.

 

2.setState를 조금 더 깊게 알아보자

setState를 알려면 본인의 소스코드에 대해서 잘 살펴보아야 합니다.

 

CRA(create-react-app) 보일러 플레이트를 생성하면 node_modulsReact디렉토리가 생성되면서

react/umd/react.development.js 파일에서 소스코드를 살펴 볼 수 있다.

 

아래는 setState의 실제 소스코드이다.

 

/**
* Sets a subset of the state. Always use this to mutate
* state. You should treat `this.state` as immutable.
*
* There is no guarantee that `this.state` will be immediately updated, so
* accessing `this.state` after calling this method may return the old value.
*
* There is no guarantee that calls to `setState` will run synchronously,
* as they may eventually be batched together.  You can provide an optional
* callback that will be executed when the call to setState is actually
* completed.
*
* When a function is provided to setState, it will be called at some point in
* the future (not synchronously). It will be called with the up to date
* component arguments (state, props, context). These values can be different
* from this.* because your function may be called after receiveProps but before
* shouldComponentUpdate, and this new state, props, and context will not yet be
* assigned to this.
*
* @param {object|function} partialState Next partial state or function to
* produce next partial state to be merged with current state.
* @param {?function} callback Called after state is updated.
* @final
* @protected
*/
Component.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Code를 살펴보면 setState는 인자로 partialStatecallback을 받도록 만들어 놓았다.

 

partialState는 ObjectFunction을 인자로 받고 다음 partial state를 현재 statemerge한다.

 

동기적으로 state가 업데이트 되지 않는다는 점은 불편할 수 있겠지만, setStatestate가 변경될때마다
새롭게 render된다는 점을 생각해보면 여러번의 render를 한번으로 줄일 수 있어 이점이 굉장히 뛰어나보인다.

 

* You can provide an optional callback that will be executed when the 
call to setState is actually completed.

 

setStatecallback을 인자로 넣어 실행하면 setState가 실제로 완료된 이후에 콜백이 실행된다고 한다.

callback을 넣어 setState를 싱행한다면 다음과 같은 형태일 것이다.

setState(partialState, ()=>{console.log(this.state)})

이렇게 하면 partialStateupdate되고 console.log로 스테이트를 찍기 때문에 동기적으로 작동하는 것 처럼 보이겠다!

 

 Callback에도 setState를 넣을 수 있다.

하지만 추전하지 않는 방식이다.

이유는 callbacksetState를 넣으면 렌더가 실제로 두번 된다.

 

보통 callbacksetState를 넣을때는 의존적인 partialState를 업데이트 할때이다. 예를 들면 아래와 같다.

this.setState={price:5000, count: 0, subTotal:0);

setSubTotal() {
   setState({ count:this.state.count+1 }, () => {
     setState({subTotal: this.state.price * this.state.count}) 
   });
};

업데이트 count를 가지고 subTotal을 업데이트해야 하기 때문에 이런 식으로 callbacksetState를 넣어서
많이 사용했었다.

 

위에서 말했던대로 렌더가 두번 되는 아주 좋지않는 코드다.

렌더를 한번 하려면 어떻게 setState해야 할까?

그래서 partialStatefunction을 받도록 되어있는 것이었다.위의 안좋은 코드들은 function form으로 바꿔놓으면

아주 깔끔한 코드가 될 것 이다.

setSubTotal(){
  setState((previousState)=>{
    return {
      count:previousState.count+1
    }
  });
  setState((previousState)=>{
    return {
      sutTotal:previousState.price * previousState.count
    }
  });
};

위의 형식은 previousState를 인자로 받아 Object를 return하는 function을 setState의 인자로 받는다.

 

이렇게 setState를 하면 렌더가 한번! 일어나고 나서 의존적인 state의 경우에도 누락되지 않고 정상적으로 업데이트

되도록 만들 수 있다.

 

참고

 

[React]setState는 비동기 작동한다

setState가 react에서 뭐하는 놈?

medium.com