홍준혁

새로나온 React 18 빠르게 살펴보기. 본문

React.js

새로나온 React 18 빠르게 살펴보기.

홍준혁 [Hong-JunHyeok] 2022. 1. 24. 09:32
728x90

 

첫 번째 기능 - Automatic Batching 

setState는 호출될 때마다 리렌더링이 일어나게 된다. 

정말로 그럴까? 사실 리액트에서는 조금 다르게 동작한다.

 

호출될 때마다 리렌더링을 작업하게 된다면 정말 비효율적인 방법일 것이다. 다음 코드를 보도록 하자.

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

  function handleClick() {
    setCount(c => c + 1); // 리렌더링 발생
    setFlag(f => !f); // 리렌더링 발생
    set... // 리렌더링
    set... // 리렌더링
    set... // 리렌더링
    set... // 리렌더링
    set... // 리렌더링
    set... // 리렌더링
  }

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

코드에서 보기만 해도 굉장히 비효율적인 방법인 것 같다. 

그래서 리액트 팀에서는 이를 더 효율적인 방법으로 동작하게 만들었다.

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

  function handleClick() {
    setCount(c => c + 1); // 아직 참아
    setFlag(f => !f); // 아직 아니야
    // 지금 리렌더링 해.
  }

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

그러면 쓸데없는 리렌더링을 줄일 수 있어 더 좋아 보인다.

하지만 그런 리액트에서도 예외가 존재하는데, 바로 브라우저 이벤트 핸들러에서 동작하는 게 아닌 경우 위와 같은 기능이 동작하지 않는다.

 

리액트 18 버전에서는 브라우저 이벤트 핸들러에서만 동작하는 게 아니라 모든 상황에서 Automatic Batching이 동작한다.

즉, Timeout, Promise등과 같은 종류들에 동일하게 자동으로 Batching이 동작하게 된다는 뜻이다.

 

그런데 그런 동작을 원하지 않을 수도 있다. 

그럴 경우엔 flushSync를 사용하면 된다. 

import { flushSync } from 'react-dom'; // React가 아니라 React-Dom이다.

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // 즉각적으로 Rendering이 시행된다.
  flushSync(() => {
    setFlag(f => !f);
  });
  // 즉각적으로 Rendering이 시행된다.
}

사실, flushSync는 권장하는 기능은 아니다. 그래서 알아서 잘 사용하면 될 것 같다.

 

두 번째 기능 - startTransition

사용자가 검색창에서 검색을 할 때, 리액트상에서 소스코드는 다음과 같을 것이다.

// 인풋은 긴급히 업데이트가 되어야 하는 사항이다.
setInputValue(input);

// 결과를 표시하는 것은 긴급한 사항이 아니다. (인풋에 비해서)
setSearchQuery(input);

리액트 버전 17까지는 모든 업데이트를 반영해 즉시 렌더링 해왔었다.

그렇게 되면 모든 것이 렌더 될 때까지 사용자의 인터렉션은 차단되게 되는 것이다.

 

그래서 startTransition이라는 기능이 나왔다.

import { startTransition } from 'react';

// 타이핑한 내용을 긴급히 보여줘야함.
setInputValue(input);

// 내부의 모든 상태를 Transition상태로 변경
startTransition(() => {
  // Transition: 결과 표시
  setSearchQuery(input);
});

즉 startTransition으로 매핑된 업데이트는 지금부터 급하지 않은 동작으로 취급하게 된다. 

즉시 처리되어야 하는 동작인 setInputValue가 먼저 처리되고 만약 startTransition이 setInputValue의 지속적인 인터렉션으로 stale 해지면 가장 최신의 업데이트만 반영되게 된다.

Urgent updates reflect direct interaction, like typing, clicking, pressing, and so on.
Transition updates transition the UI from one view to another.

- React team.

그러면 위 작업이 throttle이나 debounce 같은 작업이랑 다른 점이 무엇일까?

바로 스케쥴링이 되지 않는다는 점이다.

 

startTransition은 즉시 실행 함수고 래핑 된 함수는 동기적으로 동작하게  된다. 그러면 그 함수에서 발생하는 업데이트가 transition으로 마킹되고 리액트는 업데이트에 따른 렌더링을 할 때 이 정보를 활용한다.

 

또한 setTimeout내에서 큰 변화가 있다면 다른 인터렉션들은 블로킹되게 된다. 하지만 startTransition은 블로킹되지 않는다.

 

마지막으로 setTimeout은 업데이트를 지연시킨다는 느낌이라면 transition은 pending상태를 트래킹 할 수 있게 된다. (즉, 기다리는 동안 피드백을 받을 수 있다는 의미다.)

useTransition이라는 Hook이 있는데 이를 이용해서 pending상태 값을 받아 사용할 수 있다.

import { useTransition } from 'react';

const [isPending, startTransition] = useTransition();
{isPending && <Spinner />}

세 번째 기능 - NEW Suspense SSR Architecture (⭐️⭐️이번 업데이트의 핵심⭐️⭐️)

리액트에서 SSR을 지원하기 위해서 구조적으로 개편을 하였다.

Suspense를 이제 공식적인 기능으로 지원함으로써 리액트 앱을 더욱 작은 단위들로 분리하고 서버에서 필요한 자원을 받아서 렌더링 하는 과정이 컴포넌트 단위별로 독립적으로 동작하게 됨으로써 앱 전체를 무너뜨리지 않게 해 준다.

 

그러면 기존에 SSR에 어떤 문제점이 있었기에 이 기능이 나오게 되었는지 알아보도록 하자.

서버에서 모든 데이터를 불러와야 했기 때문에 그 데이터를 Fetching 하는 기간 동안 리액트는 Hydrating을 하지 못한 때 서버의 데이터를 기다려야 했다.

 

Hydrating이란 무엇일까?

기본적으로 리액트 SSR 애플리케이션 플로우는 다음과 같다.

  1. 서버가 UI를 그리기 위해 필요한 데이터를 패칭 한다.
  2. 서버가 전체 앱을 HTML으로 렌더링을 하고, 클라이언트에 응답을 보낸다.
  3. 클라이언트는 자바스크립트 번들을 다운로드한다.(HTML과 별개)
  4. 마지막으로, 클라이언트는 자바스크립트 로직을 HTML으로 연결한다.

이때 마지막 4번째 단계인 (마지막으로, 클라이언트는 자바스크립트 로직을 HTML으로 연결한다.)가 Hydrating 하는 작업이라고 보면 될 것 같다. 

위 화면이 서버에서 받은 HTML이라고 해보자.

그러면 Hydrating이 된다면 다음과 같을 것이다.

즉, Fetching 해야 할 데이터가 많을 경우에 사용자는 오랜 시간 동안 화면을 보지 못할 것이다.

이제 지금까지의 SSR의 문제였고 이를 해결하기 위한 react팀의 노력은 <Suspense>를 사용하여 페이지의 부분 부분을 따로 렌더링 처리하는 것이다.

<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>

위 코드와 같이 Suspense 처리를 해준다면 HTML을 렌더링 하기 위해 Comments를 기다릴 필요가 없다.

그 기간 동안 fallback에 전달된 컴포넌트가 임시로 보이기 때문에 Comments의 데이터가 다 불러와지면 그때 인터렉티브 하게 렌더링 하는 방식이다.

 

지금까지 React18의 새로 나온 기능들을 간단하게 소개해보았다.

리액트 18을 사용하려면 @rc태그를 사용하여 설치하면 된다.

npm install react@rc react-dom@rc

리액트 18은 현재 RC단계에 스테이징 되고 있고(2021-1-24 기준) 며칠 뒤에 정식 Release가 될 예정이다.

728x90
Comments