setState인자에 함수를 넣어주기

조민호·2023년 1월 25일
0

보통 우리는 setState 함수를 사용할 때 , 함수의 인자로 그 다음 상태로 사용할 ‘값’ 을 넣어줍니다

그렇지만 setState 함수를 사용할 때 , 함수의 인자로 함수를 넣어주는 경우도 있습니다

아래의 코드를 보면 우리는 한번의 클릭으로 value의 값이 3씩 늘어날 것을 예상했지만

setState의 비동기 처리 방식 때문에 실제로는 1씩 늘어나게 됩니다

function App() {
  const [value, setValue] = useState(0)

  const onClick = () => {
    setValue(value+1)
    setValue(value+1)
    setValue(value+1)
  }

  return (
    <>
      <button onClick={onClick}>+</button>
      {value}
    </>
  )
}

export default App


💡 상태 업데이트 함수의 인자를 말하는거지 상태 업데이트 함수를 감싸고 있는 함수 onClick의 인자를 말하는게 아닙니다

이에 대한 해결책으로 setState에 값을 그대로 전달하는 것이 아니라 함수를 전달하는 방법이 있습니다


기존 값을 어떻게 업데이트 할 지에 대한 함수를 등록하는 방식으로도 값을 업데이트 할 수 있는 것입니다

위의 App컴포넌트를 아래와 같이 수정합니다


function App() {
  const [value, setValue] = useState(0)

  const onClick = () => {

	 // 상태 업데이트 함수의 인자로 함수를 사용합니다
    setValue((prev) => prev + 1)
    setValue((prev) => prev + 1)
    setValue((prev) => prev + 1)
  }

  return (
    <>
      <button onClick={onClick}>+</button>
      {value}
	// 값이 1씩 증가합니다
    </>
  )
}

export default App

setValue 를 사용 할 때 그 다음 상태를 파라미터로 넣어준것이 아니라,
기존 값을 어떻게 업데이트 할 지에 대한 함수를 파라미터로 넣어주었습니다.

  • 위에서 Setter함수의 인자로 함수를 넣어주는데 prev라는 변수가 인자로 사용됐습니다

  • prev라는 변수는 이전에 선언하지 않은 새로운 변수입니다

  • 이건 useState의 기본 특성인데 상태업데이트 함수에 인자로 함수를 넘겨주면,

    인자로 넘겨준 함수의 인자는 이전 상태값을 의미하는 것입니다.

    여기서의 prev는 setValue()가 실행되기 직전의 최신 state를 의미합니다

  • 그러므로 이전 state 값을 인자로 받고 새로운 state객체를 반환하는 함수를 넣어준 것입니다

💡 이전 상태값을 말로 풀어보자면 , setState함수가 진행된다면 이 setState함수가 진행되기 직전의 가장 최신상태의 값을 말하는 것입니다

setState()인자로 함수를 받게 되면
setState()가 비동기적으로 동작한다는 사실은 변함이 없지만, 
인자로 넘겨받은 함수들은 Queue에 저장되어 순서대로 실행됩니다
따라서 첫번째 setState() 함수가 실행된 후 리턴값으로 업데이트 된 state가 두번째 함수의 인자로 들어가는 방식으로 state의 최신 상태가 유지되는 것입니다

그러므로 useState의 setState함수가 비동기적으로 업데이트될 때 , 그걸 해결하는 방법으로도 사용되곤 합니다


다른 예제

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 }); //await 때문에 여기서 3초간 멈췄다가
    setCount(count + 1);   //이게 실행이 됨
  }

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

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

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

  • Reset 버튼 : Count를 0으로 리셋
  • Increment 버튼 : Count를 1 증가
  • Increment Async 버튼 : 일정 시간이 지난 후 Count를 1 증가

우리의 직관대로라면 Count가 0일 때, Increment Async 버튼을 클릭 후 3초 이내에 Increment 버튼을 6번 누르면 3초뒤에 결과는 7이 되어야 합니다

그렇지만 실제로는 3초 이내에 아무리 여러 번 클릭한다 해도,
3초뒤의 결과는 무조건 1이 됩니다

Increment 버튼을 여러 번 클릭했는데, 1이 출력되는 이유는 

  1. handleIncrementAsync() 함수가 **호출되는 시점의 count는 0**이고

  2. 3초동안 Increment를 여러번 클릭해서 count를 갱신해도

  3. 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

전달된 함수의 인자에는 현재 버전의 state가 인수로 전달됩니다

  1. handleIncrementAsync() 함수의 count는 처음 호출 시점의 값인 0이 아닌

  2. Increment를 여러번 클릭해서 count가 계속 갱신되고 3초 뒤에

    setCount를 진행 할때, 진행하기 직전의 최신 count값을 가지게 되는 것입니다

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

출처 :

7. useState 를 통해 컴포넌트에서 바뀌는 값 관리하기

[React]React Hook setState에 함수 전달

https://velog.io/@juunghunz/ReactuseState-setState-%EC%9D%B8%EC%9E%90-%EA%B0%92-%ED%95%A8%EC%88%98

profile
웰시코기발바닥

0개의 댓글