컴포넌트에서 setState를 실행하면 컴포넌트 외부의 값을 변경하는 것이기 때문에(클로져), 상태가 변경된 직후 리렌더링 되기 전까지는 이전의 값을 그대로 참조한다.
컴포넌트가 다시 랜더링 될 때 useState를 호출해서 변경된 외부 값을 가져오고 화면에 반영한다.
이를 예시 코드로 써보면 다음과 같다.
let _value;
export useState(initialValue){
if (_value === 'undefined') {
//초기값 설정
_value = initialValue;
}
const setValue = newValue => {
//set함수로 전역에 있는 _value값을 재할당
_value = newValue;
}
//리렌더링 되고 useState()가 다시 호출될때 전역에 있는 _value를 반환
return [_value, setValue];
}
여기서 setState()를 연속적으로 호출할 때 이에 대한 처리가
값으로 인자를 전달할 때와, 함수로 전달할 때가 다르다.
setState()에 인자로 값을 전달하면 batch에 묶인 상태변화들 중 마지막의 상태변화 로직만 반영한다. (덮어쓰기)
즉, 마지막에 호출된 setState가 앞선 setState들을 덮어 쓴다.
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
setCount(count + 4);
};
//리랜더링 했을 때 count는 0+4= 4가 된다.
// setCount(+1)은 setCount+4에 덮여쓰여진다.
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount((count) => count + 1);
setCount((count) => count + 1);
};
함수를 매개변수로 넘기면 batch로 묶인 상태변화들을 queue(FIFO)에 담아서 처리한다.
facebook 개발자 (redux만든사람 트윗)
함수를 매개변수로 setState()를 여러번 호출하는 것은 안전하다. 상태 업데이트들은 큐에 저장되어 호출된 순서대로 실행 될 것이다.
async/await
, then/catch
, setTimeout
, fetch
와 같이 비동기적인 태스크를 실행할 때는 묶여서 처리되지 않는다. (각각의 setState마다 랜더링이 일어난다) const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
const [counter3, setCounter3] = useState(0);
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
setRenderCount(renderCount + 1);
}, [counter1, counter2, counter3]);
const handleClickAsync = async () => {
await setCounter1(counter1 + 1);
setCounter2(counter2 + 1);
setCounter3(counter3 + 1);
}
const handleClickThen = () => {
Promise.resolve().then(res => {
setCounter1(counter1 + 1);
setCounter2(counter2 + 1);
setCounter3(counter3 + 1);
});
}
// 두 함수 모두 3번의 랜더링을 일으킨다.
//CODE SANDBOX
[https://codesandbox.io/s/vibrant-worker-11odn?from-embed=&file=/src/App.js:109-725](https://codesandbox.io/s/vibrant-worker-11odn?from-embed=&file=/src/App.js:109-725)
ReactDOM.unstable_batchUpdate
를 사용하면 batch를 강제할 수 있다. const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
const [counter3, setCounter3] = useState(0);
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
setRenderCount(renderCount + 1);
}, [counter1, counter2, counter3]);
const handleClickThen = () => {
Promise.resolve().then(res => {
ReactDOM.unstable_batchedUpdates(() => {
setCounter1(counter1 + 1);
setCounter2(counter2 + 1);
setCounter3(counter3 + 1);
});
});
}
//한번의 랜더링만 일으킨다.
//CODE SANDBOX
[https://codesandbox.io/s/brave-keldysh-eq7zr?from-embed=&file=/src/App.js:143-664](https://codesandbox.io/s/brave-keldysh-eq7zr?from-embed=&file=/src/App.js:143-664)
“In future versions (probably React 17 and later), React will batch all updates by default so you won’t have to think about this”, according to Dan Abramov.
참고자료
https://medium.com/swlh/react-state-batch-update-b1b61bd28cd2
https://velog.io/@juunghunz/ReactuseState-setState-인자-값-함수
https://velog.io/@seongkyun/React의-setState가-비동기-처리되는-이유