보통 우리는 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
기존 값을 어떻게 업데이트 할 지에 대한 함수
를 등록하는 방식으로도 값을 업데이트 할 수 있는 것입니다
위의 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()가 비동기적으로 동작한다는 사실은 변함이 없지만,
인자로 넘겨받은 함수들은 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개의 버튼이 존재합니다.
우리의 직관대로라면 Count가 0일 때, Increment Async 버튼을 클릭 후 3초 이내에 Increment 버튼을 6번 누르면 3초뒤에 결과는 7이 되어야 합니다
그렇지만 실제로는 3초 이내에 아무리 여러 번 클릭한다 해도,
3초뒤의 결과는 무조건 1이 됩니다
Increment 버튼을 여러 번 클릭했는데, 1이 출력되는 이유는
handleIncrementAsync() 함수가 **호출되는 시점의 count는 0**
이고
3초동안 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
전달된 함수의 인자에는 현재 버전의 state가 인수로 전달됩니다
handleIncrementAsync() 함수의 count는 처음 호출 시점의 값인 0이 아닌
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