본문 바로가기
개발/React

Functional Update : 상태변경이 비동기적으로 이루어질때

by 안뇽! 2023. 7. 11.
반응형

https://betterprogramming.pub/you-dont-know-usestate-until-you-ve-used-functional-updates-5da52117620f

 

You Don’t Know useState Until You’ve Used Functional Updates

When we might need useState functional updates and how to use them

betterprogramming.pub

위 글을 번역 및 요약했습니다.

 


Functional Update

functional update는 다음과 같은 방식이다.

const [state,setState] = useState(0)

const handleButton = () => {
	// 왜 setState(state+1)하지 않고 익명함수를 이용하는 걸 까
	setState(()=>state+1)
}

익명함수를 이용하여 state를 변경하는 방식은 state 변경이 비동기 로 발생하는 상황에서 사용하면 좋다. 그게 무슨말이지?

 

일단 일반적인 상황부터 살펴보자. 보통의 상태관리는 다음과 같다.

const [state, setState] = useState(initialValue);

한편, 다음과 같이 setState에 값을 넣지 않고 함수를 넣어주어도 잘 작동한다. 앞서 말했듯이 이를 functional updates라고 한다.

setState(currentState => {
  const newState = modify(currentState);
  return newState;
});

 

언제 functional updates를 사용해야 할까?

대부분의 동기적 환경에서는 일반적인 setState(값)이 효과적이다. 그러나 여러 곳에서 공유되는 state가 비동기적으로 관리되어야 하는 상황에서는 functional updates를 고려해보면 좋다.

 

비동기 함수를 통해 state를 관리할때, 새로운 state를 계산하는 시점에서의 current state는 변경된 시점에 이미 outdated되는 경우가 있기 때문이다.

 

functional update의 예시

위와 같은 버튼 두개를 만들것이다.

 

동기적인 상태 변경만 필요한 경우

function App() {
  const [counter, setCounter] = useState(0);

  function handleIncrement() {
    setCounter(counter + 1);
  }

  return (
    <Wrapper>
      <div> Counter Value : {counter} </div>
      <div className='button-wrapper'>
        <button onClick={handleIncrement}>Increment</button>
      </div>
    </Wrapper>
  );
}

다음과 같이 버튼을 클릭하면 숫자가 1씩 증가한다.

 

비동기적 상태변경

// wait 함수를 추가해주었다.
function wait({ milliseconds }: { milliseconds: number }) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

function App() {
  const [counter, setCounter] = useState(0);

  function handleIncrement() {
    setCounter(counter + 1);
  }

  async function handleIncrementAsync() {
    await wait({ milliseconds: 2000 });
    setCounter(counter + 1);
  }

  return (
    <Wrapper>
      <div> Counter Value : {counter} </div>
      <div className='button-wrapper'>
        <button onClick={handleIncrement}>Increment</button>

        <button onClick={handleIncrementAsync}>Increment asynchronously</button>
      </div>
    </Wrapper>
  );
}

 

Increment asynchronously버튼을 클릭하여 비동기적으로 state를 변경했을때, 

 

새로운 state를 계산하는 시점에서의 current state는 변경된 시점에 이미 outdated되는 것을 발견할 수 있다.

 

1. Increment를 3번 클릭 : counter value = 3

2. Increment asynchronously를 1번 클릭 : counter value = 3에서 2초후 4로 갱신될 예정

3. Increment를 4번 클릭 : counter value = 7

4. 2초후 counter value: 4 (2번 시점에서의 outdated한 current value가 적용되었기 때문)

 

wait 함수가 끝났을때의 counter value는 여전히 이전 값을 참조하고 있다.

 

functional update

 

state를 functional 하게 update해보자

setState(currentState => {
  const newState = modify(currentState);
  return newState;
});

 

funtional update를 사용한 코드는 다음과 같다.

 

function wait({ milliseconds }: { milliseconds: number }) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

function App() {
  const [counter, setCounter] = useState(0);

  function handleIncrement() {
    setCounter(counter + 1);
  }

  async function handleIncrementAsync() {
    await wait({ milliseconds: 2000 });
    setCounter((counter) => counter + 1); // 변경된 부분
  }

  return (
    <Wrapper>
      <div> Counter Value : {counter} </div>
      <div className='button-wrapper'>
        <button onClick={handleIncrement}>Increment</button>

        <button onClick={handleIncrementAsync}>Increment asynchronously</button>
      </div>
    </Wrapper>
  );
}

아까와 같이 setState시점에서의 currentState가 outdated되지 않는 것을 확인할 수 있다.

 

functional update에서는 비동기 작업에 의해 oudated 되는 값이 없이 모든 클릭수를 카운트 할 수 있다.

반응형