medium과 react 공식 문서를 통해 리액트의 일괄 처리가 어떻게 동작하는지 정리해보고자 한다.
이 부분을 짚어보기 전에 먼저 react의 setState
와 useState
가 어떤 방법으로 동작하는지부터 짚어볼 필요가 있을 것 같다.
리액트 공식문서에 의하면,
setState()
가 하는 일은 컴포넌트의 state 객체에 대한 업데이트를 실행하고, state가 변경되면 컴포넌트는 리렌더링된다고 적혀있다.
나는 이전에 setState()
가 즉각적인 요청이라고 생각하였고 변경되는 즉시 리렌더링 된다고 생각하였다. 하지만, 공식문서를 더 살펴보니
"
setState()
는 컴포넌트를 갱신하는 데에 있어 즉각적인 명령이 아니라 요청이라고 생각하시기 바랍니다. 인지 성능(Perceived Performance)의 향상을 위하여 React는 이 메서드의 실행을 지연시키고 여러 컴포넌트를 한번에 갱신할 수도 있습니다. React는 state 변화가 즉시 적용되는 것을 보장하지 않습니다. "
즉, setState()
는 컴포넌트를 항상 즉각적으로 갱신하지는 않고, 오히려 여러 변경 사항과 함께 일괄적으로 갱신하거나, 나중으로 미룰 수도 있다는 것이다.
이로 인하여 setState()
를 호출하자마자 state에 접근하는 것에도 잠재적인 문제가 있을 수 있다고도 한다.
다시한번 정리하자면,
React는 상태 값을 업데이트 할 때 모든 요청에 따라 바로바로 rerender가 되는것이 아닌 변경사항을 모아서 한번에 일괄 처리(batch update)를 하고, 이 일괄 업데이트를 통해 컴포넌트의 렌더링 횟수를 최소화하는 것이다. (성능 향상 측면)
-> 불필요한 렌더링 방지
또한, setState 는 이벤트 핸들러 내에서 비동기적으로 작동한다.
이로 인해 부모와 자식이 모두 click 이벤트에서 setState를 호출한다면 자식은 두 번 렌더링되지 않는 대신 React는 브라우저 이벤트가 끝날 시점에 state를 일괄적으로 업데이트하고, 이는 더 큰 규모의 앱에서 뚜렷한 성능 향상을 만들어낸다.
그렇다면, 언제, 어떻게 batch update가 일어날까?
import React, { useState, useEffect } from "react";
export default function App() {
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 handleClick = () => {
setCounter1(counter1 + 1);
setCounter2(counter2 + 1);
setCounter3(counter3 + 1);
};
return (
<div className='App'>
<h1>Function Component</h1>
<div>
Counter1: {counter1}
</div>
<div>
Counter2: {counter2}
</div>
<div>
Counter3: {counter3}
</div>
<br/>
<div>Component was rendered {renderCount} times</div>
<button onClick={handleClick}>Click me</button>
</div>
);
}
해당 코드에서 Component was rendered 의 횟수는 처음 useEffect가 렌더링되는 횟수를 포함해서 1을 기점으로 한 번 클릭시 +1씩 증가한다.
생각과 달리 클릭 시 구성 요소는 세 가지 상태가 별도로 변경되지만 한 번만 렌더링되고, 이는 일괄 업데이트 덕분에 가능한 것이다.
이벤트 핸들러가 비동기 처리 방식으로 실행될 경우
-> async/await, then/catch, setTimeout, fetch 등
일괄 처리가 모든 상황에서 작동하는 것은 아니다. 위와 같이 예외 상황도 존재한다.
로딩 스테이트가 컴포넌트 안에서 밖으로 따로 분류됨 -> suspence / 서버 사이드 렌더링
<App>
<Header />
<Posts />
<App />
위와 같은 코드 예시에서 현재는 posts가 렌더링 될 때까지 헤더는 보이지 못하는 문제가 있을 수 있다.
하지만 18버전에서부터는, 느린 컴포넌트를 기다릴 필요 없이 서스펜스를 이용해 애플리케이션을 빠르게 렌더링 할 수 있게 되었다.
느린 컴포넌트가 백엔드에서 로딩 되고 나면, 리액트가 이것을 프론트로 보내서 http stream을 사용해 스크린에 띄워주는 형태이다.
서버 컴포넌트?
백엔드에서 존재하는 react js 코드를 쓸 수 있게 되어진다
서버가 렌더링할 컴포넌트인지 클라이언트가 렌더링할 컴포넌트인지 미리 선택할 수 있음
-> 이는 로딩 타임이 더 빨라지고 ux가 더 향상된다는 장점을 가져다준다.
디비랑 직접적으로 커뮤니케이션 하는 리액트 컴포넌트가 생긴다.
파일 이름 끝에 server와 client를 작성해주는 방식 / 범용은 이전 그대로
react 18부터는 렌더링 엔진이 동시 렌더링으로 변경 되었다..