devych

about everything

React setstate

2019-05-04 devychreact

오늘은 React와 관련된 첫 블로그를 적어보려 한다! React를 통해 웹 개발을 하다보면 당연스럽게 만지게 되는 주요 메소드들이 있다. 그 중에 하나가 setState다.

setState란?

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

하지만 setState를 사용하다보면 state가 정상적으로 업데이트되지 않거나 조금 느린 템포로 업데이트되는 상황을 종종 찾아볼 수 있다.

그럼 이제 왜 그런 상황이 발생하는지 좀더 자세히 알아보도록 하자

setState를 조금 더 깊게 알아보자

호랑이를 잡으려면 호랑이 굴로 들어가야 하듯이, setState를 알려면 react의 소스코드를 살펴보아야 한다.

react app 보일러 플레이트를 생성하면 node modules에 react디렉토리가 생성되면서 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는 인자로 partialState와 callback을 받도록 만들어 놓았다.

partialState는 Object와 Function을 인자로 받고 다음 partial state를 현재 state와 merge한다.

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

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

setState에 callback을 인자로 넣어 실행하면 setState가 실제로 완료된 이후에 콜백이 실행된다고 한다. callback을 넣어 setState를 실행한다면 다음과 같은 형태일것 같다.

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

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

callback에도 setState를 넣을 수 있다.

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

왜냐하면 callback에 setState를 넣으면 렌더가 실제로 두번 된다.

내가 보통callback에 setState를 넣을때는 의존적인 partialState를 업데이트할때였다. 예를 들면 아래와 같다.

this.state={price:5000, count:0, subTotal:0};

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

업데이트된 count를 가지고 subTotal을 업데이트해야 하기 때문에 이런 식으로 callback에 setState를 넣어서 많이 사용했었다… 잘못했습니다..

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

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

그래서 partialState에 function을 받도록 되어있는 것이었다. 위의 안좋은 코드를 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의 경우에도 누락되지 않고 정상적으로 업데이트 되도록 만들 수 있다.