React 18 update

dana·2024년 2월 17일
1

React.js

목록 보기
20/20

2022년 3월 29일에 올라왔던 React v18.0을 읽고 해당 내용을 정리한 글입니다.번역본이 아니기 때문에 빠지거나 주관적인 내용이 있습니다.

What is Concurrent React?

React 18은 concurrent renderer 위에서 개발되었습니다.

  • Concurrency : 하나의 기능이라기 보단, 동시에 여러 버전의 UI가 준비될 수 있는 매커니즘. 세부 실행 방식으로도 이해할 수 있음.

이를 구현하기 위해 priority queue나 multiple buffering같은 섬세한 기술들을 이용했지만, 개발자들이 이에 대해 이해하고 있을 필요는 없도록 설계되었습니다. 따라서 개발자들은 어떤 방식으로 concurrency가 이뤄질 지 고민하는 것보단, 유저가 어떤 유저 경험을 가지면 좋겠는지, 그 경험을 어떻게 전달할 것인지에 집중하며 개발하면 됩니다. 또한 Concurrency가 무엇인지만 이해하고 있으면 됩니다.

Concurrency에서 가장 중요한 포인트는 렌더링이 언제든 방해될 수 있다는 점입니다.

  • 구 버전: uninterrupted, synchronous transaction
    • 기존에는 렌더링되는 동안 interaction이 불가
  • React 18 : interrupted
    • 렌더링 중간에 방해받더라도, 동시에 UI가 나타남이 보장됨. 이게 가능하기 위해, 전체 트리가 계산이 끝날 때 까지 DOM mutation이 기다림.
    • 메인 쓰레드를 방해하지 않고, 새로운 스크린을 그릴 수 있음.
    • 큰 렌더링이 생기더라도 유저의 반응에 즉각적으로 응답할 수 있게됨.
    • Reusable state : 이전의 UI를 재사용하여 세션별로 렌더링을 쪼개 보여줄 수 있음.
      • 이를 구현하기 위한 <offscreen>이라는 컴포넌트 구현 예정
        • 2024.02 update <Activity>로 이름이 변경되고, 개발 우선순위가 낮아짐

Gradually Adopting Concurrent Feature

대부분의 이전 컴포넌트에서 별다른 변경없이도 "그냥 작동"은 하고 있지만, 어떤 컴포넌트들은 마이그레이션이 필요합니다. 전체적인 마이그레이션 방법은 기존 코드의 변경 없이 어플리케이션이 React 18에서 동작하도록 하는 것입니다. <StrictMode>를 사용하면 production에는 영향을 미치지 않고, 개발 중에 concurrent 관련 에러를 잡기 조금 수월할겁니다.

업그레이드를 하면 바로 concurrency 관련 기능들을 사용할 수 있습니다 (startTransition이나 useDefferredValuer같은). 장기적으로는concurrency를 직접적으로 사용하는 적용보다는 concurrent가 가능한 라이브러리나 프레임워크를 사용하길 기대하고 있습니다. (=maintainer들아 일해라)

Suspense in Data Frameworks

몇몇 라이브러리에서는 data fetching을 위한 Suspense를 사용할 수 있습니다. 즉각적인 data fetching에서 Suspense를 사용하는 것이 기술적으로는 가능하지만, 보편적인 방식으로는 권장하지 않습니다.

  1. Reading the data
    data를 읽을 때마다 로딩상태에서 무엇을 보여줄 지 결정해야함

  2. Spectify the loading state
    한 페이지 내에 두 컴포넌트가 로딩 상태를 가질 때 이 두 컴포넌트의 로딩 상태를 합치고 싶은 경우

    두 문제를 해결하기 위해 suspense를 사용

추후에는 특정 프레임워크가 아니더라도 suspense를 사용할 수 있도록 하는게 목표입니다. 가장 suspense가 잘 동작하는 방법은 본인의 어플리케이션 아키텍쳐에 깊에 통합되는 것입니다.

이전 버전에서는 suspense가 React.lazy와 함께 클라이언트에서 코드를 분리하는 역할로 사용되었지만, 코드를 기다리는 것 너머의 역할이 될 것입니다. (선언된 같은 suspense fallback에서 비동기적 오퍼레이션들을 다룰 수 있도록 - data fetching만을 위한게 아닌 비동기적인 이미지를 위한 fallback이라던가..)

(image from React 18 conf keynote)

Server Components is Still in Development

Server component는 서버와 클라이언트를 걸쳐 어플리케이션을 개발할 수 있도록 해주는 기능입니다.Concurrent rendering과 기술적으로 연관되어있지는 않지만, 함께 사용했을 때 더욱 잘 동작할 수 있도록 설계되었습니다.
(현재 Next에서 app directory와 함께 사용 가능 - 사용 가능한 프레임워크들은 여기에 업데이트될 예정)

What' s New in React 18

New feature : Automatic Batching

기존 방식의 경우 React event handler 내부의 업데이트만 batch가 실행되고, Promises, setTimeout, native event handler 들은 리액트에서 기본적으로 배치를 지원하지 않았습니다.

하지만 데이터를 가져온 뒤에 상태를 업데이트하는 경우, (기본 event handler X가 아닌 경우) batch가 실행되지 않고

    function handleClick () {
    // (1) 렌더링
       fetchSth().then(() => {
    //    (2) 렌더링
          setCount(c => c+1);
          setFlag(f => !f);
       })
    }

callback에서는 (1) event가 종료된 이후에 (2)를 실행하기 때문에 (1)번과 (2)번 두 번의 렌더링이 발생됩니다. (본 글의 표현을 빌리자면 run "after" not "during")

반면 React 18에서는 어떤 이벤트에서 실행됐는지와 관계없이 항상 batch로 동작합니다. (Promises, setTimeout, native event handler 다 포함)

    function handleClick () {
       fetchSth().then(() => {
          setCount(c => c+1);
          setFlag(f => !f);
       })
   // 딱 끝나는 시점에서 렌더
    }

New feature: Transitions

transition은 급한(urgent) 업데이트와 급하지 않은(non-urgent)를 구분하기 위한 개념입니다.

  • urgent transition : 타이핑, 클릭과 같은 유저 인터랙션 즉각 반영
    • UI가 즉시 바뀌지 않으면 에러로 간주됨.
  • transition updates : 다른 뷰로 UI가 변경되는 것
    • 유저가 변경이 바로 이뤄질 것이라고 기대하지 않음

예를 들어, 필터버튼을 클릭했을 때,

  • 필터 버튼이 변경됨 = urgent transition
  • 필터된 내용이 로딩 이후 나타남 = transition updates

startTranstion API를 이용해 어떤 액션이 중요도를 가지는 지 알려줄 수 있습니다.

import { startTransition } from 'react';

// Urgent
setInputValue(input);


startTransition(() => {
  // Transition
  setSearchQuery(input);
});

startTransition에 들어간 업데이트는 다른 중요한 업데이트가 생기는 경우 언제든 우선순위가 밀려날 수 있습니다. 만약 transition이 유저에 의해 중단되면 React는 완료되지 않은 오래된 렌더링 작업을 버리고 최신 업데이트만 렌더링합니다.

  • useTransition : transition을 시작하기 위한 hook. [isPending, startTransition]을 리턴
  • startTransition : hook을 사용할 수 없을 때, transition을 시작하기 위한 method.

New Suspense Features

Suspense를 사용하면 컴포넌트 트리의 일부가 아직 표시될 준비가 되지 않은 경우 로딩 상태를 선언적으로 지정할 수 있습니다:

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

Suspense는 UI의 로딩상태를 일급 선언적 개념으로 만들어, 그 위에 고차원적인 기능을 구축할 수 있게 합니다. 추후 기능은 더 확장될 예정이며, transition과 함께 사용시 충분한 데이터를 받기 전까지 렌더링을 미뤄, 안좋은 로딩 상태가 보여지는 것을 막을 수 있게됩니다.

New Client and Server Rendering APIs

React DOM Client

react-dom/client에서 export되는 API들

  • createRoot : render나 unmount를 위한 루트를 생성하는 메소드. ReactDOM.render 대신에 사용되며, 리액트 18의 새로운 기능들은 createRoot를 사용해야 적용됨
  • hydrateRoot : 서버에서 렌더된 어플리케이션을 hydrate하기 위한 메소드. ReactDOM.hydrate 대신 사용되며, 리액트 18의 새로운 기능들은 hydrateRoot 사용해야 적용됨

두 메소드 모두 새 옵션인 렌더나 hydration 중 에러가 발생했다가 회복된 경우 로그에 안내하주는 onRecoverableError를 옵션으로 받습니다. 옛날 브라우저에서는 reportError나 console.error로 동작합니다.

React DOM Server

react-dom/server에서 export되는 API들

  • renderToPipeableStream : Node 환경에서 스트리밍용
  • renderToReadableStream : edge 환경에서 스트리밍용 (Deno, Cloudflare)

기존에 있던 renderToString도 동작하지만 권장되지는 않습니다.

New Strict Mode Behaviors

리액트에서 상태를 유지하며 세션별 UI를 더하거나 뺄 수 있도록 개발될 예정입니다.이게 동작하기 위해선 같은 컴포넌트 상태를 unmount하고 remount할 때 사용해야 합니다. 이에 컴포넌트는 탄력적으로 대응할 수 있어야합니다. 이 문제 해결을 위해 Strict Mode에서 모든 컴포넌트가 첫번째 렌더 이후 두번째로 이전 상태를 복구해 자동으로 unmount되고 remount 되는 옵션을 추가했습니다.

New hooks

useId

서버와 클라이언트에서 모두 유니크한 아이디를 만들어줄 수 있는 훅입니다. 서버와 클라이언트간 hydration 불일치를 해결해줍니다. 데이터 key로의 사용은 권장되지 않습니다.

useTransition

업데이트가 급하지 않은 상태임을 표시하는 훅입니다. 해당 훅이 사용되지 않는 업데이트는 모두 urgent update로 간주됩니다.

useDefferedValue

non-urgent 업데이트의 리렌더링을 미루는 훅입니다. 디바운싱과 비슷하지만, 고정된 딜레이 시간 없습니다.

useSyncExternalStore

외부 스토어가 스토어 업데이트를 강제로 동기화하여 동시 읽기를 지원할 수 있는 훅입니다. 외부 데이터를 얻을 때, useEffect를 사용하지 않아도 됩니다. 해당 코드는 application의 직접적인 사용보단, 라이브러리에서의 사용을 위해 개발되었습니다.

useInsertionEffect

CSS-in-js 라이브러리의 렌더링에서 스타일 주입의 성능 문제 해결을 위한 훅입니다. DOM mutaion 이후, layout effect가 새 layout을 읽기 전에 동작합니다. 이미 CSS-in-js를 구축해두었다면 사용을 권장하지 않습니다.

profile
PRE-FE에서 PRO-FE로🚀🪐!

2개의 댓글

comment-user-thumbnail
2024년 2월 18일

깔끔한 정리 감사합니다

1개의 답글