본문으로 바로가기

React 18 매력적인 기능

category software engineering/frontend 2023. 5. 1. 22:24
728x90

소개

메이저 버전 업데이트는 조심스러워야하고 불편하게 느껴지는게 사실이지만 가장 매력적으로 느껴졌던 React 18 특징들을 소개해보겠습니다.

 


 

조금 긴 배경

React 는 JavaScript 로 만들어진 언어입니다. 그리고 자바스크립트는 아시다시피 아래 모든 과정을 단일 프로세스(싱글 쓰레드)로 진행하죠. React 또한 컴포넌트를 업데이트 할 때 메인 쓰레드 위에서 모든 프로세스가 끝날때까지 하나의 블록을 업데이트 하는 동기식으로 작동합니다.

 

 

사용자 이벤트 또한 메인 쓰레드에서 발생 하므로 변경 사항이 생겨 업데이트가 발생한다면 현재 실행중인 렌더링이 끝날 때 까지 입력 이벤트와 같은 이벤트가 발생하더라도 처리할 수 없습니다. 메인 쓰레드를 차단하는게 문제라면 웹 워커를 사용해서 다른 쓰레드 내부에서 작업을 처리하면 되지 않을까요? 라고 생각이 들 수 있는데 이미 누군가 React 팀에 질문을 했는데, “웹 워커에서 reconsolidation 과 v-dom 을 처리하도록 위임 하더라도 대부분 경우에서 퍼포먼스 향상에 도움이 되지 않아 현재 작업을 좀 더 유연하게 만드는 방법을 찾았고 async rendering 을 조만간 발표하겠다.” 고(2017) 답변했습니다.

 


 

이후 React 팀은 CPU 작업과 IO 작업에 특정 패턴의 문제를 발견하였는데 그 중 일부를 병렬로 실행하기 전 이게 퍼포먼스 문제가 아닌 스케줄링 문제라는점을 알게 되었습니다. 그래서 낮은 우선순위와 높은 우선순위를 구분하여 렌더링을 하다 유저 인풋이 들어오면 애니메이션 을 멈추고 이후에 스케줄링이 포함된 Async Rendering 을 포함한 Fiber 를 React 16 에 포함시켜 배포합니다.

 


 

그러다 다음년도 10월 Async Rendering 의 이름을 Concurrent Rendering 으로 재정의하며

Concurrent React can work on multiple tasks at a time, and switch between them according to priority. Also can partially render a tree without committing the result.

다양한 작업을 한번에 수행할 수 있고, 우선 순위에 따라 순서를 변경할 수 있습니다. 또한 결과를 commit 하지 않고 트리를 부분적으로 렌더할 수 있다고 소개합니다.

 

 

이후 19년 Concurrent mode 를 optional 로 제공하며 사용자들과 소통하며 React 17 에서 다양한 버전을 포함시키는 등의 노력을 지나 첫 번째 PR 이후 5년 뒤인 21년 12월 마침내 Concurrent Feature 가 React 18에 자동으로 포함됩니다.

 


 

특징

useDeferredValue

검색이 가능한 데이터 테이블 혹은 리스트가 useDeferredValue 를 사용하기 좋은 예 입니다. ‘아이스크림’ 을 검색한다고 할 때 ‘아' 를 검색하면 ‘이' 를 검색하기 전 리액트에서 렌더를 시작했다면 렌더의 우선 순위가 높기에 '이’가 검색되고 필터되는데 UX 측면에서 덜 효과적일 수 있습니다.

기존 useState 가 아닌 useDeferredValue 를 통해 우선 순위를 높여준다면 렌더링과정을 멈추고 '이'를 먼저 검색하고 이후 다시 렌더링을 하기 때문에 더 나은 UX 를 제공할 수 있습니다.

기존 debounce 와 비교하더라도 지정한 시간만큼 강제되는 기다림이 있기에 훨씬 효과적입니다.

 

startTransition

우선 순위가 낮은 업데이트를 리액트에 알려줄 수 있습니다. useDeferredValue 와 비슷한 상황인 작업량이 많아 결과를 보여주기 위한 UI 제공이 느리거나, 네트워크가 느린 경우에 효과적입니다.

function handleChange(e) {
  setHighPriorityInput(e.target.value);
  
  React.startTransition(() => {
    setLowerPrioritySearch(e.target.value);
  })
}

 


 

Suspense

특정 컴포넌트 트리가 보여질 준비가 안됐을 때 특정 로딩 UI 를 보여줄 수 있습니다. 17에서 Code Splitting 만 지원했지만 18 이후 data fetching 라이브러리, 서버 렌더링을 지원합니다. 기존 각 컴포넌트 마다 로딩을 보여주는거 보다 상위 컴포넌트에 로딩을 제공하여 더 나은 UX 를 제공할 수 있습니다.

 


 

React Server Component (RSC)

모두 SSR 에는 익숙하겠지만 RSC 라는 새로운 Feature 가 등장했습니다. 우선 소개에 앞서 세 가지를 알고 가면 앞으로 내용에 조금 도움이 됩니다.

  • Server Component (.server.js) - 서버에서만 렌더링되는 컴포넌트
    • 데이터 베이스, 내부 서비스, 파일 시스템과 같은 server-only 데이터 사용 가능
    • 클라이언트 컴포넌트 props로 serializable 한 데이터 전달 가능
    • ❌ useState, useReducer, useEffect, DOM 조작, 브라우저 api 사용이 제한됩니다.
  • Client Component (.client.js) - 클라이언트 혹은 SSR 을 통해 렌더링 되는 기존 컴포넌트
    • 기존 React 코드와 동일
  • Shared Component (.js) - 서버와 클라이언트 둘 다 에서 유동적으로 사용될 공용 컴포넌트
    • 서버에서 사용될 수 있어 서버 컴포넌트와 동일한 제한사항

 

우선 아이디어는 유지보수성을 높히기 위해 각 Component 에서 API를 호출했는데, 매 컴포넌트에서 서버로 데이터를 호출하다보니 Spinner 가 돌고 클라이언트에가 서버에 요청하고 응답하는 과정이 반복되어 퍼포먼스적 이슈가 발생하는 부분에서 시작했다고 합니다.

사실 Facebook 팀은 Relay 와 GraphQL 을 통해 필요한 데이터를 한번에 가져와서 해결했지만 작은 규모 프로젝트 혹은 API 노출 등 기타 이유로 GraphQL 을 사용하지 못하는 경우에 사용할 수 있습니다.

 

 

주요 특징으로는 SSR 에서 getServerSideProps 를 통해 backend 기능을 사용했었지만, 이제 RSC 를 이용하면 컴포넌트 모든 영역에서 서버의 접근이 가능합니다.

 

그리고 RSC 로 만든 코드는 클라이언트로 전달되지 않아 번들사이즈를 줄이는데 효과적입니다.

// NoteWithMarkdown.js: *before* Server Components

import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}
// NoteWithMarkdown.server.js - Server Component === zero bundle size

import marked from 'marked'; // zero bundle size
import sanitizeHtml from 'sanitize-html'; // zero bundle size

function NoteWithMarkdown({text}) {
  // same as before
}

Webpack을 보면 서버 컴포넌트는 안보입니다
HTML을 렌더하는게 아니라 위 처럼 트리형태로 전달하고 렌더합니다

 

use()

뿐만 아니라 새롭게 등장한 use() 훅을 서버 컴포넌트에서 사용하면 비동기 함수를 만들지 않고 React Component 자체로 데이터를 렌더링할 수 있습니다. 이를 통해 데이터를 가져오는 부분과 렌더링 하는 부분을 나눠 서버와 클라이언트 컴포넌트를 명확하게 구분할 수 있습니다.

function TooltipContainer({showTooltip}) {
  const promise = fetchInfo();
  
  if(!showTooltip) {
    return null;
  } else {
    return <Tooltip content={use(promise)} />
  }
}

 

'software engineering > frontend' 카테고리의 다른 글

Style, CSS 뭐 써볼까?  (0) 2023.09.21
QueryClient 뭐 써볼까?  (0) 2023.09.21
TL;DR shadow DOM  (0) 2023.04.15
React Server Component (RSC)  (0) 2023.04.15
React - Use() Hook  (0) 2023.03.29