React/React 문법

[React]React Hook setState에 함수 전달

DevStory 2021. 11. 22.

이번 포스팅에서는 React Hook을 사용한 함수형 컴포넌트에서 setState에 함수를 전달했을 경우 동작하는 방법을 소개합니다.

 


setState에 함수가 아닌 값을 전달

React Hook에서는 useState()를 사용하여 state의 값을 초기화합니다.

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

그리고 특정 이벤트 또는 로직에 의해 setState()에 원하는 값을 인수로 전달하여 state의 값을 갱신할 수 있습니다.

handleClick = () => {
  setState(newState);
}

React Hook에서 state의 값을 갱신하는 방법은 위 방법처럼 setState()에 원하는 값(객체 또는 값)을 전달할 수 있으며, 함수를 전달하는 방법도 있습니다.

 

다음과 같이 setState()에 함수를 전달하는 경우 해당 함수는 state가 갱신되기 이전의 값을 바탕으로 계산됩니다.

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

currentState는 갱신되기 이전의 현재 state를 의미하며, update() 함수가 반환하는 값이 state에 갱신됩니다.


언제 setState에 함수를 전달해야 하는가?

state 갱신을 동기적으로 해야 하는 경우 setState()에 값(객체 또는 값)을 전달하는 방법으로도 충분합니다.

 

만약, state를 공유하고 비동기적으로 처리해야 하는 경우 setState()에 값이 아닌 비동기 함수를 전달할 수 있지만, 코드가 복잡해지는 문제가 발생합니다.

 

그리고 state가 원하지 않는 값으로 갱신될 수 있습니다.


setState에 함수를 전달하는 예제 및 내용

다음은 setState()에 state를 공유하고 비동기적으로 처리해야 하는 경우 잘못된 코드와 해결 방법을 설명합니다.

 

다음은 설명에 사용할 예제입니다.

예제

3개의 버튼이 존재합니다.

Reset 버튼 : Count를 0으로 리셋

Increment 버튼 : Count를 1 증가

Increment Async 버튼 : 일정 시간이 지난 후 Count를 1 증가

 

비동기 함수가 비정상적으로 동작하는 경우

import React, { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  // Reset 버튼
  function handleReset() {
    setCount(0);
  }

  // Increment 버튼
  function handleIncrement() {
    setCount(count + 1);
  }

  // 비동기 함수인 Increment Async 버튼
  async function handleIncrementAsync() {
    await wait({ miliseconds: 3000 });
    setCount(count + 1);
  }

  function wait({ miliseconds }) {
    return new Promise((resolve) => setTimeout(resolve, miliseconds));
  }

  return (
    <React.Fragment>
      Count: {count}
      <br />
      <button onClick={handleReset}>Reset</button>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleIncrementAsync}>Increment Async</button>
    </React.Fragment>
  );
}

 

wait() 함수는 지정된 시간 후에 완료되는 함수입니다.

function wait({ miliseconds }) {
  return new Promise((resolve) => setTimeout(resolve, miliseconds));
}

 

handleIncrementAsync() 함수에 async-await를 사용하여 비동기적으로 처리합니다.

async function handleIncrementAsync() {
  await wait({ miliseconds: 3000 });
  setCount(count + 1);
}

Count가 0일 때, Increment Async 버튼을 클릭 후 3초 이내에 Increment 버튼을 여러 번 클릭하면, 결과는 1로 출력됩니다.

 

Increment 버튼을 여러 번 클릭했는데, 1이 출력되는 이유는 handleIncrementAsync() 함수가 호출되는 시점의 count는 0입니다.

 

Increment 버튼을 여러 번 클릭해서 count의 값이 갱신되더라도 handleIncrementAsync() 함수의 count는 호출 시점의 값인 0을 참조하고 있기 때문입니다.

 

useCallback을 사용한다면?

React Hook에서 지원하는 useCallback API를 사용하면, 비동기 함수가 정상적으로 동작할 것이라고 생각할 수 있습니다.

const handleIncrementAsync = useCallback(async () => {
  await wait({ miliseconds: 3000 });
  setCount(count + 1);
}, [count, setCount]);

handleIncrementAsync() 함수가 count에 종속되어있더라도 동일한 상황이 발생합니다.

 

Functional Update

처음에 말했듯이 setState에는 함수를 전달할 수 있습니다. 전달된 함수는 현재 버전의 state가 인수로 전달되고 이러한 방법을 "Functional Update"라고 합니다.

async function handleIncrementAsync() {
  await wait({ miliseconds: 3000 });
  setCount((count) => count + 1);
}

위 코드처럼 setState()에 함수를 전달할 수 있습니다.

 

그리고 다음 코드처럼 함수를 정의 및 선언하여 setState()에 함수명으로 전달할 수 있습니다.

async function handleIncrementAsync() {
  await wait({ miliseconds: 3000 });
  setCount(countIncrement);
}

function countIncrement(value) {
  return value + 1;
}

 

반응형

댓글