전혀 생각하고 있지 못했는데 항해에서 같이 공부하는 팀원분이 제안해주신 내용이다.
react setState() 복습하면서 함수형 업데이트와 일반형 업데이트의 차이로 batch를 하느냐 마느냐가 나오는데,
왜 batch update를 하고, 함수형 업데이트는 batch update를 하지 않는지 이해해보면 좋을거 같습니다.
역시 공부는 혼자가 아니라 같이 하면 훨씬 더 얻을게 많고 유익하다는 것을 느꼈다.
리액트에서 state
값을 변경하면 해당 컴포넌트와 자식 컴포넌트들에 리렌더링이 일어난다.
하지만 그렇다고 state
가 여러개일 때 업데이트가 일어난다고 여러번 리렌더링이 일어나지는 않았다.
state
를 각각 업데이트하여 바로 반영하는 것 대신 batch update 하여
렌더링 횟수를 최소화하고 성능을 향상시켜준다.
React 17 및 이전 버전은 브라우저 이벤트에 대한 일괄 처리만 지원합니다. 그러나 React 18 업데이트에서는 자동 배칭이라는 향상된 배칭 버전을 도입했다.
이렇게 하면 출처에 관계없이 모든 상태 업데이트에 대한 일괄 처리가 활성화됩니다.
React 17은 브라우저 이벤트 및 Hooks에 대한 일괄 처리만 지원했다.
const Click = () => {
const [additionCount, setAdditionCount] = useState(0);
const [subtractionCount, setSubtractionCount] = useState(0);
console.log("Click Component Rendering");
const handleOnClick = () => {
setAdditionCount(additionCount + 1);
setSubtractionCount(subtractionCount - 1);
};
return (
<div>
<button
style={{ width: "50%", height: "30%" }}
onClick={() => {
handleOnClick();
}}
>
Click Me!
</button>
<div>Add Count: {additionCount}</div>
<div>Substraction Count: {subtractionCount}</div>
</div>
);
};
export default Click;
위와 같이 여러상태를 가지고 변경해도 한 번만 렌더링 될 것이다.
브라우저와 상관없는 컨텍스트에서의 상태변경
const Click2 = () => {
const [additionCount, setAdditionCount] = useState(0);
const [subtractionCount, setSubtractionCount] = useState(0);
console.log("Click2 Component Rendering");
const handleOnClickAsync = () => {
fetch("https://jsonplaceholder.typicode.com/todos/1").then(() => {
setAdditionCount(additionCount + 1);
setSubtractionCount(subtractionCount - 1);
});
};
return (
<>
<div>
{additionCount}
{subtractionCount}
</div>
<button
style={{ width: "50%", height: "30%" }}
onClick={() => {
handleOnClickAsync();
}}
>
Click Me Async Call!
</button>
</>
);
};
export default Click2;
fetch()
비동기 함수 내부에서 상태 두개를 변경했더니
두 번 렌더링이 되었다!
작은 규모의 앱에서는 상관 없겠지만
이런 불필요한 리렌더링이 중복해서 발생할 경우 자식 컴포넌트들에도 영향을 끼칠 것이고
큰 성능문제를 야기할 수 있다.
결국 하나의 컴포넌트에서 어떤 상태들이 변경될 경우 일괄적으로 업데이트 되어야 하는 것이었다.
왜 이제 useState
가 비동기로 동작하는지 이유를 알 수 있었다.
17.0.2 버전으로 구성한 코드샌드박스다.
18버전으로 변경하여 테스트해보자!
Batch update 기능 때문에 상태변화를 감지하고 넣어두었다가 일괄처리한다.
const onNormalClick = () => {
setCount(count + 1);
console.log("click1");
console.log(count);
setCount(count + 5);
console.log("click2");
console.log(count);
};
위의 상태변경은 setCount(count + 5)
만 수행되는 것 처럼 보일 것이다.
상태 업데이트 시점에 이미 가지고 있던 초기값 count
를 사용해 5를 더해준다.
전달된 같은 스코프의 setState
들을 병합해 최종적으로 한 번 수행한다.
setState
로 업데이트 시 값을 전달하는게 아니라 함수를 전달할 수 있다.
상태 반영 때 함수를 순서대로 호출하며 그 먼저 수행된 결과를 state로 반영하게 되고
다음 변경 함수가 그 state를 사용한다.
const onFunctionalClick = () => {
setCount((count) => count + 1);
console.log("click1");
console.log(count);
setCount((count) => count + 1);
console.log("click2");
console.log(count);
};
이전 상태 변경이 순차적으로 반영된다고 리렌더링을 그만큼 하고 있지는 않다.
동기 처리를 한다고 표현하기 보다는 리렌더링 시 모든 변경을 순차적으로 반영해줄 수 있게 해주는 것으로 표현할 수 있지 않나 생각한다.
업데이트(setState
)를 수행하는 행위 자체는 컴포넌트 관점에서 비동기니까??
setState
가 있는 스코프 내에서는 어차피 count
가 이전 값으로 나온다.
리렌더링 전-후 가 확실히 나뉘어있고 일괄업데이트를 하는 것 처럼 보인다.
단지 배치 업데이트 시 상태 변경 동작을 덮어씌우느냐 큐에 쌓아줄 수 있느냐의 차이이지 않을까 하고 이해를 해보려 한다