React 18 Features

September 16, 2021

자동 배치

React 18은 기본적으로 더 많은 일괄 처리를 수행하여 즉시 사용 가능한 성능 향상을 추가하여 애플리케이션 또는 라이브러리 코드에서 수동으로 업데이트를 일괄 처리할 필요가 없습니다.

Batching 이란?

Batching은 더 나은 성능을 위해 React가 여러 상태 업데이트를 단일 재렌더링으로 그룹화 시키는 것을 의미합니다. 간단히 말해서 Batching (그룹화)는 여러 상태에 대한 업데이트가 단일 렌더링으로 결합됨을 의미합니다.

함수 내에서 변수를 변경하기 위해서 setState를 사용할 때마다 React는 각 setState에서 렌더링 하는 대신 모든 setState를 수집한 다음 함께 실행합니다. 이를 Batching (일괄처리) 라고 합니다.

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    setCount(c => c + 1); // 아직 렌더링 되지 않습니다.
    setFlag(f => !f); // 아직 렌더링 되지 않습니다.
    // 해당 function이 끝나면 일괄적으로 처리합니다. (이것이 Batching 입니다!)
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

이것은 불필요한 재 렌더링을 피하기 때문에 성능상에 이점이 있습니다.

그러나, React는 batching을 수행할때 일관성을 유지하지 않았습니다. 이것은 React가 브라우저 이벤트 (예: 클릭이벤트) 동안만 일괄적으로 업데이트를 사용했기 떄문입니다. 그러나 여기서는 이벤트가 이미 처리된 후 fetch callback 에서 상태를 업데이트 합니다.

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    fetchSomething().then(() => {
      // react 17 혹은 이전 버전에서는 이 동작들이 callback 안에서 이벤트 후에 실행되어 실행중에는 일괄 업데이트를 수행하지 않습니다.
      setCount(c => c + 1); // 리렌더링이 발생합니다.
      setFlag(f => !f); // 리렌더링이 발생합니다.
    });
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

일괄 처리하지 않으려면?

일반적으로 일괄 처리의 경우 안전성을 보장하나, 일부 코드의 경우 상태 변경 직후 DOM에서 무언가를 읽는데에 의존할 수 있습니다. 이러한 경우 ReactDOM.flushSync() 를 사용하여 일괄 처리를 opt-out (제외) 시킬 수 있습니다.

SSR (Server-Side-Rendering)

서버 렌더링은 JS 데이터를 서버에서 HTML로 렌더링하여 프런트엔드에서 계산을 저장하는 방법 입니다. 그 결과 대부분의 경우 초기 페이지 로드가 빨라집니다.

React는 4개의 순차적 단계로 서버렌더링을 수행합니다.

  1. 서버에서 각 Component에 대한 데이터를 가져옵니다.
  2. 서버에서 전체 앱이 HTML로 렌더링 되어 클라이언트로 전송됩니다.
  3. 클라이언트에서 전체 앱의 Javascript 코드를 가져옵니다.
  4. 클라이언트에서 Javascript는 React를 Hydration이라고 하는 서버에서 생성된 HTML에 연결합니다. React 17버전 까지에서 SSR은 페이지 hydration을 시작하기 전에 전체 페이지를 로드해야 했습니다.

이것은 React18에서 React component를 작은 단위로 청킹하여 사용할 수 있게 변경됩니다.

Streaming HTML

<Suspense fallback={<Spinner />}>
  {children}
</Suspense>

컴포넌트를 래핑하여 페이지의 나머지 부분에 대해서는 HTML 스트리밍을 시작하는 내용을 기다릴 필요가 없다고 React에 알립니다. 대신 React는 로딩 스피너를 보냅니다.

데이터가 서버에서 준비되면, React는 추가 HTML을 동일한 스트림에 보내고 해당 HTML을 올바른 위치에 넣기 위한 최소한의 인라인 스크립트 태그를 보냅니다.

선택적 hydration

React 18 이전에는 앱의 전체 Javascript 코드가 로드되지 않은 경우 hydration을 수행할 수 없었습니다. 더 큰 앱의 경우 이 프로세스에 시간이 걸릴 수 있습니다.

React 18부터는 컴포넌트가 로드되기 전에 앱을 hydration 할 수 있습니다.

컴포넌트를 래핑하면, 페이지의 나머지 부분이 스트리밍 및 hydration을 차단해서는 안된다고 React에 전파할 수 잇습니다. 즉, 더이상 hydration을 시작하기 위해서 모든 코드가 로딩될 때까지 기다릴 필요가 없습니다. React는 로드되는 부분을 hydration 할 수 있습니다.

전환 (startTransition)

startTransition의 중요한 사용 사례중 하나는 사용자가 검색 input에 입력을 시작 할 때입니다. 검색 결과가 사용자가 예상한 대로 몇 밀리초 대기하는 동안 입력값은 즉시 업데이트 되어야 합니다.

이 API는 빠른 업데이트와 지연된 업데이트를 구별하는 방법을 제공하고 있습니다. 지연된 업데이트 (즉, UI에서 다른 UI로 전환) 하는 내용을 전환 업데이트라고 합니다. (Transition Updates)

타이핑, Hover, Click과 같은 긴급 업데이트의 경우 일반적으로 다음과 같이 props / function을 호출합니다.

setText(input)

긴급하지 않거나 무거운 UI 업데이트의 경우 다음과 같이 startTransition API에 래핑시킬 수 있습니다.

startTransition(() => setText(input));

새로운 Root API

일반적으로 아래와 같은 루트 수준의 DOM을 만들고 React APP을 추가합니다. 이것은 더이상 사용되지 않으며 레거시 루트 API라고 칭하게 됩니다.

import React from 'react';
import ReactDOM from 'react-dom';

const container = document.getElementById('root') 

ReactDOM.render(<App />, container);

대신에 새로운 Root API가 React 18에 도입되었습니다.

import React from 'react';
import ReactDOM from 'react-dom';
import App from 'App'

const container = document.getElementById('root')

const root = ReactDOM.createRoot(container)

root.render(<App />)

React 18은 React 17 앱을 React 18로 원활하게 전환할 수 있도록 레거시 Root API및 새로운 Root API를 제공합니다.

Reference: Are you ready for React 18? - DEV Community

...