● react 팀이 18 버전을 통해 해결하고자 했던 문제들?
3월 29일, React 18 버전이 released 됐습니다. React 18 버전에는 크게 아래와 같은 변화가 있었다고 합니다.
- New Root API
- Automatic Batching
- New Concept “Transition”
- SSR support for Suspence
React 팀이 18 버전을 통해서 개선하고자 했던 내용은 무엇 일까요?
여러 가지 개선 사항이 있지만 그중 두 가지만 살펴보자면, 첫 번째는 Batching 작업을 통한 Rendering 퍼포먼스 향상, 두 번째는 새로운 기능인 Transition 을 통해 UI 업데이트에 우선순위를 부여하거나, 느린 네트워크 환경에서의 UX 향상을 살펴볼 수 있을 것 같습니다.
● New Root API: 새로운 Root API 의 등장
18 버전이 등장하면서 새로운 Root API 가 등장 했습니다. 여기서 언급하는 루트는 React 가 랜더링을 위해 가상 돔 트리를 추적하는 데 사용하는 최상단에 위치한 데이터 구조 포인터를 의미합니다. 즉, public 폴더 하에 index.html 을 살펴보면 <div id=”root”></div> 라는 요소를 살펴볼 수 있는데, 이 요소가 루트라고 할 수 있습니다.
// 18v 이전의 루트
ReactDOM.render(, document.getElementById(‘root’));
// 18v 이후의 루트 API
import * as ReactDOMClient from ‘react-dom/client’;
const container = document.getElementById(‘app’);
const root = ReactDOMClient.createRoot(container);
root.render();
현재 버전에서 CRA를 하게 되면 index.js 에서 아래의 코드와 같습니다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
❍ 새로운 Root API를 적용한 이유?
18 버전 이전의 React 에서는 루트가 되는 컨테이너에 변화가 없더라도 render 하기 위해서, 루트를 반드시 체크하고, 루트를 통과 했어야만 했다고 합니다. 이 과정은 React 가 Virtual DOM을 사용하기 때문에 반드시 거쳐야 하는 작업입니다. React 팀에서는 루트가 되는 컨테이너를 랜더링 과정을 거칠때마다, 매번 해당 루트를 통과할 필요가 없다고 생각했고, 이런 무의미한 반복되는 과정을 개선하기 위해서 새로운 Root API 를 18 버전에 적용하게 되었습니다.
제 생각에는 재렌더링 되는 과정 속에서, root가 변화하거나 그럴 일이 거의 없기 때문에 무의미한 반복과정이라고 생각하지 않았을까? 싶네요.
❍ New API “ ReactDOMClient “
새로운 API의 createRoot() 함수를 사용하면 루트를 반환합니다. 새로운 루트를 통해서 React Node 를 DOM 에 Render 할 수 있습니다. 또한 원한다면 Unmount 도 할 수 있습니다.
import * as ReactDOMClient from "react-dom/cleint";
const root = ReactDOMClient.createRoot(container);
root.render();
root.unmount();
❍ “ Root API ” 에서의 콜백 함수의 삭제
기존의 루트 (ReactDOM.render) 에서는 컴포넌트가 랜더링 되거나 업데이트될 때, 주로 콜백 함수를 render 함수의 매개변수로 사용할수는 있었지만 대부분 사용하지 않았습니다.
만약 콜백 함수가 필요한 상황이라면 render 함수의 매개변수로 넣어서 사용할 수 있었습니다. 하지만 새로운 Root API 에서는 매개변수로 사용할 수 있는 콜백 함수를 삭제 했다고 합니다. (함수의 형식 매개 변수를 삭제함 으로써 매개변수로 콜백 함수를 넣어주지 못한다는 의미입니다.)
콜백 함수를 삭제한 이유는, hydration, SSR 과 함께 콜백 함수를 사용한다면 해당 콜백 함수를 호출하는 타이밍이 개발자의 예상과는 다르게 작동할 수 있었기에, 이런 의도하지 못한 상황을 피하기 위해 매개변수 콜백 함수를 삭제한 것 입니다. 대신에 이전 버전의 콜백 함수 기능을 활용하고자 한다면 requestIdleCallback, setTimeout 혹은 루트에서 ref 콜백을 통해 사용할 수 있게 되었습니다.
● 18 버전 이후 Batching 되는 방법
18 버전 이후부터는 새로운 Root API인 createRoot 를 통해서, 브라우저 이벤트 뿐만 아니라 어떤 이벤트에서 발생하는 state 업데이트를 어디서 발생했는지를 신경 쓰지 않고 자동으로 업데이트들이 배칭 시킨다고 합니다. 즉, state 업데이트 들을 Batching 하여 여러 번 수행 했어야 했던 랜더링 과정을 단 한 번만 처리할 수 있게 해줬고, 이는 리랜더링 횟수를 최소화하여, 애플리케이션의 성능 향상을 기대할 수 있게 되었습니다. (원래라면 state가 변화할때마다 많은 렌더링이 필요했다.)
// 위 두 업데이트 모두 배칭되어서 단 한번 리랜더링 된다!
function handleClick() {
setCount((prev) => prev + 1);
setFlag((f) => !f);
}
// setTimeout 내에서 업데이트도 배칭되어 한번의 리렌더링을 하게된다.
setTimeout(() => {
setCount((prev) => prev + 1);
setFlag((f) => !f);
}, 1000);
// fetch api에서 또한 배칭되어 한번의 리렌더링을 하게된다.
fetch("api").then(() => {
setCount((prev) => prev + 1);
setFlag((f) => !f);
});
● New Feature: Transition
Transition 은 React 18 버전이 출시하며 새롭게 등장한 컨셉 입니다. 개발자는 Transition 기능을 활용한다면 보다 빠르게 업데이트되어야 하는 상황과 그렇지 않은 업데이트 상황을 구별하면서 개발을 할 수 있게 되었습니다.
18 버전 이전의 버전에서는 state를 업데이트하는데 있어서 우선순위를 두는 게 어려웠습니다. (즉 setter함수의 실행 우선순위를 구별하기 어려웠다.) 예를 들어서 Throttling, Debounce 기법을 활용하여 업데이트의 우선순위를 설정할 수 있었지만, 두 방법 모두 원하지 않는 작업 시간이 발생 한다는 문제점이 발생했습니다. 또한 위 기법을 활용하는 동안엔 어떤 컴포넌트는 업데이트에 반응을 (리랜더링) 하지 않는 문제도 발생하곤 했다고 합니다.
이런 상황에서 React 팀은 18 버전에서 Transition 이라는 기능을 출시 했습니다. 이 새로운 기능은 특수한 업데이트 들을 직접 마킹 하면서, 의도된 방식으로 작업을 지연시켜 사용자와의 상호작용 및 UX 를 지속적으로 향상시켜 줍니다.
이와 관련된 훅으로는 useDeferredValue와 useTransition 훅이 있습니다.