logo

React v18.0

[번역] React v18.0

react, javascript, typescript, translation


이제 React 18을 npm에서 사용할 수 있습니다!

최근 게시물에서 React 18로 앱 업그레이드 단계별 가이드를 공유했습니다. 이 게시물에서는 React 18의 새로운 기능과 앞으로 어떤 의미가 있는지에 대해 간략하게 설명합니다.

최신 메이저 버전에는 자동 배치(automatic batching), startTransition과 같은 새로운 API, 서스펜스(Suspense)를 지원하는 스트리밍 서버 사이드 렌더링 등의 기능이 바로 포함됩니다.

React 18의 많은 기능은 강력한 새 기능을 선보이게하는 물밑의 변화인 새로운 동시 렌더러(new concurrent renderer)를 기반으로 구축되었습니다. 동시성 React는 선택입니다 - 동시성 기능을 사용하는 경우에만 가능합니다 - 그렇지만 사람들이 애플리케이션을 만드는 방식에 큰 영향을 미칠 것이라고 생각합니다.

우리는 React의 동시성 지원을 연구하고 개발하는 데 수년을 보냈고 기존 사용자에게 점진적인 적용 방법을 제공하기 위해 각별한 주의를 기울였습니다. 지난 여름, 커뮤니티 전문가들에게 피드백을 수집하고 전체 React 생태계에 대한 원활한 업그레이드 경험을 보장하기 위해 React 18 워킹 그룹을 결성했습니다.

놓칠 경우를 대비해 전달하자면, React Conf 2021에서 이러한 비전을 많이 공유했습니다.

아래는 동시성 렌더링을 시작으로 이번 릴리스에서 기대할 수 있는 모든 내용을 정리한 것입니다.

React Native 사용자를 위한 참고 사항: React 18은 새로운 React Native 아키텍처와 함께 React Native로 제공됩니다. 자세한 내용은 React Conf Keynote를 참고하세요.

동시성 Concurrent React란?

React 18에서 가장 중요한 추가 사항은 여러분이 고려할 필요 없는 동시성입니다. 이는 애플리케이션 개발자에게 대부분 해당되지만 라이브러리 관리자에게는 조금 더 복잡할 수 있습니다.

동시성은 그 자체로 기능이 아닙니다. React가 동시에 여러 버전의 UI를 준비할 수 있도록 하는 새로운 백그라운드 메커니즘입니다. 동시성은 구현의 세부 사항으로 생각할 수 있습니다. - 기능들의 핵심이기 때문에 매우 중요합니다. React는 우선 순위 대기열(priority queues) 및 다중 버퍼링(multiple buffering)과 같은 내부 구현에서 고도의 기술을 사용합니다. 그러나 이러한 개념은 우리의 공개 API 어디에서도 확인할 수 없습니다.

우리는 API를 설계할 때 구현 세부 사항을 개발자에게 숨기려고 합니다. React 개발자는 사용자 경험을 어떻게 할 것인지 무엇에 집중하고 React는 그 경험을 어떻게 제공할 것인지 방법을 처리합니다. 따라서 React 개발자가 내부에서 동시성이 어떻게 동작하는지 아는 것을 기대하지 않습니다.

하지만, 동시성 React는 일반적인 구현 세부 사항보다 더 중요합니다. - 이것은 React의 핵심 렌더링 모델에 대한 기본 업데이트입니다. 그래서 동시성이 어떻게 동작하는지 아는 것은 그다지 중요하지 않지만, 높은 레벨에서 그것이 무엇인지 알 가치는 있을 수 있습니다.

동시성 React의 주요한 속성은 렌더링이 중단될 수 있다는 것입니다. React 18로 처음 업그레이드할 때 동시성 기능을 추가하기 전, 업데이트가 이전 버전의 React와 같게 렌더링됩니다. - 하나의 중단 없는 동기 트랜잭션으로. 동기 렌더링일 시 업데이트가 렌더링을 시작하면 사용자는 결과를 화면에서 볼 수 있을 때까지 어떤 것도 렌더링을 중단할 수 없습니다.

동시성 렌더링에선 항상 이러한 경우가 있는 것은 아닙니다. React는 업데이트 렌더링을 시작하고 중간에 일시 중지했다가 나중에 계속할 수 있습니다. 심지어 진행 중인 렌더링을 완전히 그만둘 수도 있습니다. React는 렌더가 중단되더라도 UI가 일관되게 보여지도록 보장합니다. 이를 위해 전체 트리가 평가된 후 끝까지 DOM 변화가 수행하기를 기다립니다. 이 기능을 통해 React는 메인 스레드를 차단하지 않고 백그라운드에서 새로운 화면을 준비할 수 있습니다. 즉, UI가 대규모 렌더링 작업 중이라도 사용자 입력에 즉시 응답할 수 있어서 유연한 사용자 경험을 만들 수 있다는 것을 의미합니다.

다른 예로는 재사용 가능한 상태(state)가 있습니다. 동시성 React는 화면에서 UI 섹션을 삭제한 다음, 이전 상태를 재사용하면서 추후 다시 추가할 수 있습니다. 예를 들어 사용자가 한 화면을 탭으로 이동하거나 뒤로 이동할 때, React는 이전 화면을 이전과 같은 상태로 이전 화면을 복원할 수 있어야 합니다. 곧 있을 마이너 변경점에서는 이 패턴을 구현한 <Offscreen> 이라는 새로운 컴포넌트를 추가할 계획입니다. 마찬가지로 Offscreen을 사용하여 백그라운드에서 새 UI를 준비할 수 있으므로 사용자가 UI를 확인하기 전에 미리 준비할 수 있습니다.

동시성 렌더링은 React의 강력한 새로운 도구이며 서스펜스(Suspense), 트랜지션(transitions)과 스트리밍 서버 렌더링을 비롯한 대부분의 새 기능은 이를 활용하도록 만들어져 있습니다. 그러나 React 18은 이 새로운 기반 위에 구축하고자 하는 목표의 시작에 불과합니다.

동시성 기능을 점진적으로 도입

기술적으로, 동시성 렌더링은 주요한 변경 사항입니다. 동시성 렌더링은 중단 가능하기에 이 기능을 활성화할 시 컴포넌트는 조금 다르게 동작합니다.

테스트 상으로 수천개의 컴포넌트를 React 18로 업그레이드 했습니다. 거의 모든 기존 컴포넌트들이 아무 변경없이 동시성 렌더링으로 “그냥 동작”하는 것을 알 수 있었습니다. 단, 일부는 추가적인 마이그레이션 작업이 필요할 수도 있습니다. 일반적으로 작은 변경 사항들이지만, 자신의 페이스에 따라 변경할 수 있습니다. React 18에서 새로운 렌더링 동작은 앱의 새로운 기능을 사용하는 부분에서만 활성화됩니다.

전체적인 업그레이드 전략은 기존 코드를 망가뜨리지 않고 React 18에서 애플리케이션을 실행하는 것입니다. 그런 다음 점진적으로 사용자의 페이스에 맞춰 동시성 기능을 추가할 수 있습니다. 개발하는 동안 동시성 관련 버그를 해결하기 위해 <StrictMode>를 사용할 수 있습니다. 엄격 모드(Strict Mode)는 프로덕션 동작에 영향을 주진 않지만 개발 중에는 추가 경고와 멱등성이 있을 것으로 예상되는 이중 호출(double-invoke) 함수들을 로깅합니다. 엄격 모드가 모든 것을 잡진 못하지만 가장 흔한 실수를 방지하는 데 효과적입니다.

React 18로 업그레이드를 한 뒤, 동시성 기능을 즉시 사용할 수 있습니다. 예를 들면, startTransition을 사용해서 사용자의 입력을 차단하지 않고 화면 사이를 이동할 수 있습니다. 혹은 useDeferredValue으로 비용이 비싼 재렌더를 조절할 수 있습니다.

그러나 장기적으로 앱에 동시성을 추가하는 주요한 방법으론 동시성이 활성화된 라이브러리나 프레임워크를 사용하는 것입니다. 대부분의 경우 동시성 API와 직접적으로 상호 작용하지 않습니다. 예를 들어, 개발자가 새 화면으로 이동할 때마다 startTransition을 호출하는 대신 라우터 라이브러리는 자동적으로 startTransition에서 네비게이션을 래핑합니다.

라이브러리가 동시성이 호환되도록 업그레이드하는 데 시간이 걸릴 수 있습니다. 우리는 라이브러리가 동시성 기능을 더 쉽게 활용할 수 있도록 새로운 API를 제공했습니다. 그동안, React 생태계를 점진적으로 마이그레이션하기 위해 작업하고 있으니 유지 관리자에게 인내심을 가지고 기다려 주세요.

자세한 내용은 이전 게시물을 참조하세요: React 18로 업그레이드하는 방법

데이터 프레임워크에서의 서스펜스 Suspense

React 18에선 Relay, Next.js, Hydrogen 또는 Remix와 같은 독단적인 프레임워크에서 데이터를 가져오는 데 서스펜스를 사용할 수 있습니다. 서스펜스를 사용한 애드훅(Ad hoc) 데이터 가져오기는 기술적으로 가능하지만, 여전히 일반적인 전략으로는 권장되지 않습니다.

앞으로는 아마 독단적인 프레임워크를 사용하지 않고도 서스펜스로 데이터를 더 쉽게 접근할 수 있도록 하는 추가 기본 요소를 선보일 수도 있습니다. 그러나 서스펜스는 애플리케이션 아키텍처와 깊이 통합되어 있을 때 가장 잘 동작합니다: 라우터, 데이터 레이어 및 서버 렌더링 환경. 따라서 장기적으로 볼 때, 라이브러리와 프레임워크가 React 생태계에서 중요한 역할을 할 것으로 기대합니다.

이전 버전 React와 마찬가지로, 클라이언트에서 React.lazy를 사용해서 코드 분할(code splitting)에 서스펜스를 사용할 수도 있습니다. 하지만 서스펜스에 대한 우리의 비전은 항상 코드를 불러오는 것 이상이었습니다. - 목표는 서스펜스에 대한 지원을 확장하고 결국에는 동일한 선언적 서스펜스 풀백이 모든 비동기 작업(코드 로딩, 데이터, 이미지 등)을 처리할 수 있도록 하는 것입니다.

Server Components는 아직 개발 중입니다

서버 컴포넌트는 개발자가 서버와 클라이언트에 걸쳐 앱을 빌드할 수 있게 해주는 조만간 출시될 기능으로, 클라이언트 측 앱의 풍부한 상호작용성과 기존 서버 렌더링의 향상된 성능을 결합하였습니다. 서버 컴포넌트는 본질적으로 동시성 React와 결합하여 있지 않지만, 서스펜스와 스트리밍 서버 렌더링과 같은 동시성 기능들과 함께 가장 잘 동작하도록 설계되었습니다.

서버 컴포넌트는 아직 실험적이지만 18.x 마이너 릴리즈에서 초기 버전을 출시할 예정입니다. 그러는 동안 제안(proposal)을 발전시키고 폭넓게 채택할 수 있도록 준비하기 위해 Next.js, Hydrogen 및 Remix와 같은 프레임워크와 협력하고 있습니다.

React 18의 새로운 기능

새 기능: 자동 배치 Automatic Batching

배치란 성능 향상을 위해 React가 여러 상태 업데이트를 단일 재렌더로 그룹화하는 것을 말합니다. 자동 배치 없이는 React 이벤트 핸들러 내부에서만 배치했습니다. 프로미스, 타임아웃, 네이티브 이벤트 핸들러 또는 기타 이벤트 내의 업데이트는 React에서 기본적으로 배치되지 않았습니다. 자동 배치를 사용하면 이러한 업데이트가 자동으로 배치됩니다:

// 이전: React 이벤트만 배치되었습니다.
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React는 상태 업데이트에 대해 각각 한 번씩 하며 렌더를 총 두 번 할 것입니다. (배치 아님)
}, 1000);

// 이후: 타임아웃, 프로미스, 네이티브 이벤트 핸들러 또는 기타 이벤트 내에서 업데이트는 배치됩니다.
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React는 재렌더를 마지막에 한번만 합니다. (배치입니다!)
}, 1000);

자세한 내용은 React 18의 더 적은 횟수로 렌더링을 하기 위한 자동 배치 게시물을 확인하세요.

새 기능: 트랜지션 Transitions (전환)

트랜지션은 긴급하거나 긴급하지 않은 업데이트를 구분하는 React의 새로운 개념입니다.

  • 긴급 업데이트는 입력, 클릭, 누르기 등과 같은 직접적인 상호 작용을 반영합니다.
  • 트랜지션 업데이트는 하나의 뷰에서 다른 뷰로 UI를 전환합니다.

입력, 클릭 또는 누르기와 같은 긴급 업데이트는 물리적 객체의 동작에 대한 우리의 직관이 일치하게끔 즉각적인 응답이 필요합니다. 그렇지 않으면 “잘못된” 느낌을 받습니다. 하지만 트랜지션은 사용자가 모든 중간값을 화면에서 보는 것을 기대하지 않기 때문에 다릅니다.

예를 들어, 여러분이 드롭다운에서 필터를 선택하면 필터 버튼 자체가 클릭하는 즉시 응답할 거라 예상합니다. 그렇지만 실제 결과는 따로 트랜지션될 수 있습니다. 약간의 지연은 감지할 수 없으며 자주 예상할 수 있습니다. 그리고 결과가 렌더링이 완료되기 전에 다시 필터를 변경하면 최신 결과만 볼 수 있습니다.

일반적으로 최상의 사용자 경험을 얻기 위해서는 단일 사용자 입력으로 긴급과 긴급하지 않은 업데이트 모두 발생해야 합니다. 입력 이벤트 안에서 startTransition API를 사용하여 어떤 업데이트가 긴급인지 혹은 “트랜지션”인지 React에 알릴 수 있습니다.

import {startTransition} from 'react';

// 긴급: 어떤 입력인지 표시
setInputValue(input);

// 내부의 모든 상태 업데이트를 transitions으로 표시
startTransition(() => {
  // Transition: 결과를 표시
  setSearchQuery(input);
});

startTransition에 래핑된 업데이트는 긴급하지 않은 것으로 처리되며 클릭이나 키 누르기와 같은 더 긴급한 업데이트가 들어오면 중단됩니다. 트랜지션이 사용자에 의해 중단되면 (예로 한 줄에 여러 문자를 입력), React는 완료되지 않은 오래된 렌더링 작업을 버리고 최신 업데이트만 렌더링합니다.

  • useTransition: 보류 중인 상태(the pending state)를 추적하는 값을 포함한 트랜지션 시작 훅
  • startTransition: 훅을 사용할 수 없을 때 트랜지션을 시작하는 메서드

트랜지션은 동시성 렌더링을 선택하여 업데이트를 중단할 수 있습니다. 콘텐츠가 다시 중단된 경우(re-suspends)라면, 트랜지션은 백그라운드에서 트랜지션 콘텐츠가 렌더링하는 동안 현재 콘텐츠를 계속 표시하도록 React에 지시합니다. (자세한 내용은 Suspense RFC를 참조하세요)

transitions은 여기를 참조해 주세요.

새로운 서스펜스 Suspense 기능

서스펜스는 컴포넌트 트리 일부가 아직 보여질 준비가 되지 않은 경우 로딩 상태를 선언적으로 지정할 수 있습니다:

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

서스펜스는 React 프로그래밍 모델에서 “UI 로딩 상태”를 일급 선언적 개념으로 만듭니다. 이를 통해 우리는 상위에 있는 더 높은 레벨의 기능을 구축할 수 있게 합니다.

우리는 몇 년 전에 제한된 버전의 서스펜스를 선보였습니다. 그러나, 지원되는 유일한 사용 사례는 React.lazy를 사용한 코드 분할(code splitting)이었고 서버에서 렌더링할 땐 전혀 지원되지 않았습니다.

React 18에선 서버에서 서스펜스에 대한 지원을 추가하고 동시성 렌더링 기능을 사용하여 서스펜스 기능을 확장했습니다.

React 18의 서스펜스는 트랜지션 API와 결합할 때 가장 잘 동작합니다. 트랜지션 중에 일시 중단하면 React는 이미 보이는 콘텐츠가 폴백으로 대체되는 것을 방지합니다. 대신, React는 잘못된 로딩 상태를 방지하기 위해 충분한 데이터가 로드될 때까지 렌더를 지연시킵니다.

자세한 내용은 서스펜스 in React 18. RFC를 참조하세요.

새로운 클라이언트와 서버 렌더링 APIs

이번 릴리스에서는 클라이언트와 서버에서 렌더링하기 위해 노출하는 API를 재설계할 기회를 얻었습니다. 이러한 변경은 사용자가 React 18의 새 API로 업그레이드하는 동안 React 17 모드에서 이전 API 계속 사용할 수 있습니다.

React DOM Client

이 새로운 API는 이제 react-dom/client에서 내보내집니다.

  • createRoot: renderunmount할 루트를 생성하는 새로운 메서드. ReactDOM.render 대신 사용하세요. React 18의 새 기능은 이것 없이 동작하지 않습니다.
  • hydrateRoot: 서버에서 렌더된 애플리케이션을 하이드레이팅하는 새로운 메서드. ReactDOM.hydrate 대신 React DOM 서버 API와 함께 사용하세요. React 18의 새 기능은 이것 없이 동작하지 않습니다.

createRoothydrateRoot 둘다 로깅을 위해 렌더링 또는 하이드레이션 중에 React가 오류에서 복구될 때 알림을 받기를 원한다면 onRecoverableError라는 새로운 옵션을 사용합니다. 기본적으로, React는 reportError를 사용하거나 예전 브라우저에서는 console.error를 사용합니다.

React DOM 클라이언트에 대한 문서는 여기를 참조하세요.

React DOM Server

이 새로운 API는 이제 react-dom/server에서 내보내지며 서버에서 스트리밍 서스펜스를 완전히 지원합니다.

  • renderToPipeableStream: Node 환경에서 스트리밍에 적합.
  • renderToReadableStream: Deno 및 Cloudflare workers와 같은 최신 엣지 런타임 환경을 적합.

기존에 존재하는 renderToString 메서드는 계속 동작하지만 권장되진 않습니다.

React DOM 서버에 대한 문서는 여기를 참조하세요.

새로운 엄격 모드 Strict Mode 동작

앞으로 React가 상태를 보존하면서 UI 섹션을 추가하고 제거할 수 있는 기능을 넣고자 합니다. 예를 들어, 사용자가 화면에서 뒤로 이동하는 탭을 하면 React는 즉시 이전 화면을 표시할 수 있어야 합니다. 이를 위해, React는 이전과 같은 컴포넌트를 사용해서 마운트를 해제하고 트리를 다시 마운트합니다.

이 기능은 React 앱에 뛰어난 성능 향상을 제공하지만, 여러 번 마운트되고 소멸되는 effects에 탄력 있는 컴포넌트가 필요합니다. 대부분의 effects는 변경 없이 동작하지만, 일부 effects는 한 번만 마운트되거나 소멸된다고 추정합니다.

이러한 문제를 해결하기 위해 React 18은 엄격 모드에 새로운 개발 전용 검사를 도입했습니다. 이 새로운 검사는 컴포넌트가 처음 마운트될 때마다 모든 컴포넌트를 자동으로 마운트 해제하고 다시 마운트하여 두 번째 마운트의 이전 상태를 복원합니다.

이 변경 전엔 React는 컴포넌트를 마운트하고 다음과 같은 effects를 생성합니다:

* React는 컴포넌트를 마운트합니다.
  * Layout effects가 생성됩니다.
  * Effects가 생성됩니다.

React 18의 Strict Mode를 사용하면 React는 개발 모드에서 컴포넌트의 마운트 해제와 재마운트를 시뮬레이션합니다.

* React는 컴포넌트를 마운트합니다.
  * Layout effects가 생성됩니다.
  * Effects가 생성됩니다.
* React는 컴포넌트 마운트 해제를 시뮬레이션합니다.
  * Layout effects가 소멸됩니다.
  * Effects가 소멸됩니다.
* React는 이전 상태를 가진 컴포넌트를 마운트하는 시뮬레이션합니다.
  * Layout effects가 생성됩니다.
  * Effects가 생성됩니다.

여기 재사용 가능한 상태 보장에 관한 문서를 참조하세요.

새로운 훅 Hooks

useId

useId 는 하이드레이션 불일치를 방지하면서 클라이언트와 서버에서 고유 ID 생성을 위한 새로운 훅입니다. 고유 ID가 필요한 접근성 API(accessibility APIs)와 통합되는 컴포넌트 라이브러리에 주로 유용합니다. 이렇게 하면 React 17 이하에 이미 존재하는 문제가 해결되지만, 새로운 스트리밍 서버 렌더러가 HTML을 순서 없이 전달하는 방식 때문에 React 18에선 훨씬 더 중요합니다. 여기에서 문서를 참조하세요.

useTransition

useTransitionstartTransition을 사용하면 일부 상태 업데이트를 긴급하지 않게 사용할 수 있습니다. 다른 상태 업데이트는 기본적으로 긴급한 것으로 간주됩니다. React는 긴급 상태 업데이트(예로 텍스트 입력 업데이트)가 긴급하지 않은 상태 업데이트(예로 검색 결과 리스트 렌더링)를 중단할 수 있습니다. 여기에서 문서를 참조하세요.

useDeferredValue

useDeferredValue를 사용하면 트리의 긴급하지 않은 부분에 재렌더링하는 것을 지연할 수 있습니다. 디바운싱(debouncing)과 비슷하지만 그에 비해 몇 가지 장점이 있습니다. 고정된 시간 지연이 없으므로, React는 첫 번째 렌더가 화면에 반영되는 직후 지연된 렌더(deferred render)를 시도합니다. 지연된 렌더는 중단 가능하며 사용자 입력을 차단하지 않습니다. 여기에서 문서를 참조하세요.

useSyncExternalStore

useSyncExternalStore는 동기적으로 스토어 업데이트를 강제해서 외부 스토어가 동시 읽기를 지원할 수 있는 새로운 훅입니다. 외부 데이터 소스에 대한 구독을 구현할 때 useEffect가 필요하지 않으며 React 외부 상태와 통합되는 모든 라이브러리에 권장됩니다. 여기에서 문서를 참조하세요.

Note: useSyncExternalStore는 애플리케이션 코드가 아니라 라이브러리에서 사용할 목적으로 만들어졌습니다.

useInsertionEffect

useInsertionEffect는 CSS-in-JS 라이브러리가 렌더에 스타일을 주입할 때 생기는 성능 문제를 해결할 수 있는 새로운 훅입니다. 이미 CSS-in-JS 라이브러리를 구축하지 않았다면 이 라이브러리를 사용할 거라고 기대하지 않습니다. 이 훅은 DOM이 변경된 후에 실행되지만 layout effects가 새로운 레이아웃을 읽기 전에 실행됩니다. 이렇게 하면 React 17 이하에 이미 존재하는 문제가 해결되지만, 동시성 렌더링 중에 React가 브라우저에게 양보하여 레이아웃을 다시 계산하게끔 기회를 제공하기 때문에 React 18에선 훨씬 더 중요합니다. 여기에서 문서를 참조하세요.

Note: useInsertionEffect는 애플리케이션 코드가 아니라 라이브러리에서 사용할 목적으로 만들어졌습니다.

출처