** 아래의 내용 대부분은 리액트 공식문서의 내용입니다.
https://react.dev/learn/queueing-a-series-of-state-updates
위 공식문서 페이지 제일 하단에 챌린지 문제 중 state queue를 스스로 만들어보기! 가 있다. state queue가 어떻게 동작하는지 알기 쉬운 챌린지라 이렇게 작성해본다.
예를 들어 아래와 같은 코드가 있다고 해보자.
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
</>
)
}
버튼을 클릭했을 때 나오는 number는 몇 일까?
3
일까?
아쉽게도 아니다.
1
이 나온다.
React는 state 업데이트를 하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다립니다. 이 때문에 리렌더링은 모든 setNumber() 호출이 완료된 이후에만 일어납니다.
이는 리액트의 일괄처리(배칭)라고 하는 동작 때문이다.
이렇게 하면 너무 많은 리렌더링이 발생하지 않고도 여러 컴포넌트에서 나온 다수의 state 변수를 업데이트할 수 있습니다. 하지만 이는 이벤트 핸들러와 그 안에 있는 코드가 완료될 때까지 UI가 업데이트되지 않는다는 의미이기도 합니다. 일괄처리(배칭, batching)라고도 하는 이 동작은 React 앱을 훨씬 빠르게 실행할 수 있게 해줍니다. 또한 일부 변수만 업데이트된 "반쯤 완성된" 혼란스러운 렌더링을 처리하지 않아도 됩니다.
React는 클릭과 같은 여러 의도적인 이벤트에 대해 일괄 처리하지 않으며, 각 클릭은 개별적으로 처리됩니다. React는 일반적으로 안전한 경우에만 일괄 처리를 수행하니 안심하세요. 예를 들어 첫 번째 버튼 클릭으로 양식이 비활성화되면 두 번째 클릭으로 양식이 다시 제출되지 않도록 보장합니다.
이 말은 잠시 헷갈릴 수 있는데, "각 버튼별로 일괄처리는 되지만, 모든 버튼이 일괄처리 된다는 말은 아니라는 말"이다.
기본적으로 React18 로 들어오며 Automatic Batching이 적용됐다.
https://github.com/reactwg/react-18/discussions/21
(batching 에 대한 요약된 설명)
두 개의 인수를 받게 됩니다: baseState는 초기 state(예: 0)이고, queue는 숫자(예: 5)와 업데이터 함수(예: n => n + 1)가 추가된, 순서대로 섞여 있는 배열입니다
위 문제는 다음 코드샌드박스에서도 풀어볼 수 있다.
https://codesandbox.io/s/xkix2y?file=%2FprocessQueue.js&utm_medium=sandpack
먼저, 코드를 살펴보자.
import { getFinalState } from './processQueue.js';
function increment(n) {
return n + 1;
}
increment.toString = () => 'n => n+1';
export default function App() {
return (
<>
<TestCase
baseState={0}
queue={[1, 1, 1]}
expected={1}
/>
<hr />
<TestCase
baseState={0}
queue={[
increment,
increment,
increment
]}
expected={3}
/>
<hr />
<TestCase
baseState={0}
queue={[
5,
increment,
]}
expected={6}
/>
<hr />
<TestCase
baseState={0}
queue={[
5,
increment,
42,
]}
expected={42}
/>
</>
);
}
function TestCase({
baseState,
queue,
expected
}) {
const actual = getFinalState(baseState, queue);
return (
<>
<p>Base state: <b>{baseState}</b></p>
<p>Queue: <b>[{queue.join(', ')}]</b></p>
<p>Expected result: <b>{expected}</b></p>
<p style={{
color: actual === expected ?
'green' :
'red'
}}>
Your result: <b>{actual}</b>
{' '}
({actual === expected ?
'correct' :
'wrong'
})
</p>
</>
);
}
코드가 꽤 길게 느껴져 부담스러울 수 있는데, 화면으로 보자면 별 거 없다.
위 app에서 불러오는 getFinalState
를 작성해주면 된다.
그래서 Your result:
가 Expected result:
의 수와 동일하면 통과!
export function getFinalState(baseState, queue) {
let finalState = baseState;
// TODO: do something with the queue...
return finalState;
}
처음엔 뭐지 싶었다.
힌트를 한 번 보자.
export function getFinalState(baseState, queue) {
let finalState = baseState;
for (let update of queue) {
if (typeof update === 'function') {
// TODO: apply the updater function
} else {
// TODO: replace the state
}
}
return finalState;
}
export function getFinalState(baseState, queue) {
let finalState = baseState;
for (let update of queue) {
if (typeof update === 'function') {
// TODO: apply the updater function
finalState = update(finalState)
} else {
// TODO: replace the state
finalState = update
}
}
return finalState;
}
공식문서는 여러모로 참 도움이 많이 된다.👍👍👍