이 글에서는 React 18 에서 도입된 배경과 함께 Concurrent Features 을 살펴보고, 관련된 기능인 Transition, stream SSR, Suspense 등 내용을 알아본다.
Concurrent Features 는 2022년 3월에 발표된 React 18 버전에서 정식으로 도입된 새로운 렌더링 메커니즘 이다.
Concurrent Features 도입으로 React의 렌더링을 더 유연하고 효율적으로 동작하게함으로서 복잡하고 무거운 UI 작업에서도 빠르고 부드러운 사용자 경험을 제공한다.
React 18 이전에는 렌더링은 중간에 개입할 수 없도록 “동기적으로 동작하였다.” 렌더링이 한번 시작되면 중간에 해당 작업을 중단하거나, 수정할 수 없었다.
게다가 싱글 스레드로 동작하는 자바스크립트이기에 기존 방식 대로 렌더링이 되었을 때 다음과 같은 문제가 발생했다.
예를 들어 기존 방식에서는 사용자 입력 중에 무거운 렌더링이 발생하면, 버벅이면 입력이 멈추는 것처럼 느껴졌었다.
다음은 사용자 입력마다 50000개의 DOM을 생성 하는 예제
⇒ 입력을 입력할 때 무거운 렌더링으로 인해 입력이 버벅이는 것을 확인할 수 있다.
React의 렌더링 엔진(Fiber)을 확장하여 동기/비동기 작업을 구분하고, 병렬로 처리 가능한 환경을 제공하도록 고안하고, 도입한 것이 “동시성” 개념이다.
동시성(Concurrency)은 여러 작업이 마치 동시에 실행되는 것처럼 보이는 것 처럼 동작하는 것을 의미한다. 운영체제를 공부하면서 흔하게 듣게 되는 “동시성”과 같은 의미이다.
실제로는 한 번에 하나의 작업만 처리하지만, 여러 조각으로 나누어 매우 짧은 시간 간격으로 작업을 번갈아가면서(Context Switch) 하기 때문에 여러 작업을 “동시에” 처리하는 것 처럼 느낄 수 있다.
React 팀에서는 이야기 한 바에 따르면, 우선순위 큐 및 다중 버퍼링 등을 통해 Concurrent Feature을 도입하였다고 이야기 한다. Concurrency 방식을 도입함으로서 크게 다음과 같은 변화를 가져왔다.
즉, 사용자와 직접 연결된 작업(클릭, 입력)은 빠르게 처리 하고, 오래 걸리는 작업(리스트 필터링, API 응답 반영)은 천천히 처리 하며 사용자 경험을 높였다.
👈🏻 이전 React
[A 작업 시작] → [A 완료] → [B 시작] → [B 완료]
💫 Concurrent Feature 적용 시
[A 작업 시작] → [B가 급함!] → [B 먼저 처리] → [A 마저 처리]
“여러 State 업데이트를 하나의 리렌더링으로 그룹화”
이전에는 한번의 이벤트에서 여러번의 setState를 호출하게 되면, 각각 렌더링이 발생했다면, Automatic Batching 를 도입함으로 각각의 State 업데이트를 하나의 렌더링으로 묶어 한번만 발생하도록 하였다.
// 변경 전 : Batching 없음
setTimeout(() => {
setCount(c => c + 1); // 렌더링 1
setText('hello'); // // 렌더링 2
}, 1000);
// 변경 전 : React 이벤트 핸들러 내부 에서는 Batching
useEffect(() => {
setCount(1);
setText('hello');
// 렌더링
}, []);
위와 같이 변경 전에는 useEffect() 같은 React의 네이티브 핸들러 안에서는 batching 처리가 되고 있었지만, setTimeOut 이나 Promise 안에서는 각 State 업데이트마다 한 번씩, 총 두 번 렌더링 되었다.
// 변경 후: 네이티브 이벤트 핸들러 또는 다른 이벤트들이 Batch
setTimeout(() => {
setCount(c => c + 1);
setText('hello');
// 렌더링 발생
}, 1000);
Concurrency 가 적용된 이후에는 React 네이티브 핸들러 뿐만아니라, setTimeout, EventListener, Promises 등 비동기 처리 내에서 상태를 자동으로 Batch 처리 해줌으로서 렌더링이 한번만 발생하게 된다.
“ 긴급한 업데이트와 긴급하지 않은 업데이트를 구분하기 위한 React의 새로운 개념”
입력, 클릭과 같은 상호작용은 즉각적인 반응이 필요할 때가 있다. 간혹 “최신순 버튼”, “검색 필터링” 등 과 같은 곳에서 상호작용이 일어났을 때, 느리게 진행되는 작업으로 인해 곧바로 반응이 일어나지 않으면 “잘못 됐다”고 느끼게 된다.
이럴 때 Transition 기능을 통해 “빠르게 반응해야 할 작업과느려도 되는 작업을 구분해서 처리” 할 수 있도록 할 수 있다.
startTranstion()
Transition을 시작하는 기본 메서드
const handleChange = (e) => {
const value = e.target.value;
setText(value);
// 트랜지션 직접 사용
startTransition(() => {
// 긴급하지 않는 작업 처리
const filtered = items.filter((item) =>
item.toLowerCase().includes(value.toLowerCase())
);
setList(filtered);
});
};
다음과 같이
useTransition()
보류 중인 Pending State를 추적하는 값을 포함하여 Transition을 시작하는 Hook.
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setInput(value);
// 필터링 작업은 우선순위 낮게 처리
startTransition(() => {
// 긴급하지 않는 작업 처리
const filtered = items.filter((item) =>
item.toLowerCase().includes(value.toLowerCase())
);
setList(filtered);
});
};
*“*입력값은 즉시 반영하되, 그 값을 사용하는 연산이나 렌더링은 지연시켜 UI의 반응성을 높이는 개념“
사용자가 입력할 때 input 은 바로 업데이트되지만, 비싼 연산은 deferredInput 으로 조금 늦게 처리됨
const [input, setInput] = useState("");
const deferredInput = useDeferredValue(input);
const results = useMemo(() => slowFilter(input), [deferredInput]);
비동기 작업(데이터 패칭, 코드 스플리팅 등) 이 완료될 때까지 대체 UI(fallback)를 보여주는 기능
기존 Suspense의 한계
- 코드 스플리팅(lazy loading) 을 적용하는 컴포넌트에서만 사용 가능
- 직접적으로 데이터 패칭을 기다릴 수는 없음 (라이브러리 필요)
- SSR과 완전히 호환되지 않음
개선점
Suspense를 감지해 fallback을 단계적으로 스트리밍 전송Suspense가 중첩되어 있을 때, 각기 다른 부분만 개별적으로 fallback 처리 가능function UserProfile({ id }) {
const { data } = useQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
suspense: true,
});
return <div>{data.name}</div>;
}
function App() {
return (
<Suspense fallback={<p>사용자 정보를 불러오는 중...</p>}>
<UserProfile id={1} />
</Suspense>
);
}
React 18에서 지원하는 새로운 SSR 방식
”HTML을 스트리밍 방식으로 클라이언트에 점진적으로 전달하는 기술”
🔁 전통적 SSR vs Streaming SSR
| 구분 | 전통 SSR (React 17 이전) | Streaming SSR (React 18) |
|---|---|---|
| 렌더링 방식 | 모든 컴포넌트가 렌더된 후 HTML 응답 전송 | 렌더링되는 대로 HTML 조각 조각 전달 |
| 초기 로딩 시간 | 길어짐 (모두 준비될 때까지 대기) | 짧아짐 (부분적으로 즉시 렌더링 가능) |
| 사용자 체감 속도 | 느림 | 빠름 |
(번역) React 18이 애플리케이션 성능을 개선하는 방법
Automatic batching for fewer renders in React 18 · reactwg react-18 · Discussion #21
React 18 톺아보기 - 04. Concurrent Render | Deep Dive Magic Code