2022년 3월 29일에 올라왔던 React v18.0을 읽고 해당 내용을 정리한 글입니다.번역본이 아니기 때문에 빠지거나 주관적인 내용이 있습니다.
React 18은 concurrent renderer 위에서 개발되었습니다.
Concurrency
: 하나의 기능이라기 보단, 동시에 여러 버전의 UI가 준비될 수 있는 매커니즘. 세부 실행 방식으로도 이해할 수 있음.이를 구현하기 위해 priority queue나 multiple buffering같은 섬세한 기술들을 이용했지만, 개발자들이 이에 대해 이해하고 있을 필요는 없도록 설계되었습니다. 따라서 개발자들은 어떤 방식으로 concurrency가 이뤄질 지 고민하는 것보단, 유저가 어떤 유저 경험을 가지면 좋겠는지, 그 경험을 어떻게 전달할 것인지에 집중하며 개발하면 됩니다. 또한 Concurrency가 무엇인지만 이해하고 있으면 됩니다.
Concurrency에서 가장 중요한 포인트는 렌더링이 언제든 방해될 수 있다는 점입니다.
uninterrupted
, synchronous transaction
interrupted
Reusable state
: 이전의 UI를 재사용하여 세션별로 렌더링을 쪼개 보여줄 수 있음.<offscreen>
이라는 컴포넌트 구현 예정<Activity>
로 이름이 변경되고, 개발 우선순위가 낮아짐대부분의 이전 컴포넌트에서 별다른 변경없이도 "그냥 작동"은 하고 있지만, 어떤 컴포넌트들은 마이그레이션이 필요합니다. 전체적인 마이그레이션 방법은 기존 코드의 변경 없이 어플리케이션이 React 18에서 동작하도록 하는 것입니다. <StrictMode>
를 사용하면 production에는 영향을 미치지 않고, 개발 중에 concurrent 관련 에러를 잡기 조금 수월할겁니다.
업그레이드를 하면 바로 concurrency 관련 기능들을 사용할 수 있습니다 (startTransition
이나 useDefferredValuer
같은). 장기적으로는concurrency를 직접적으로 사용하는 적용보다는 concurrent가 가능한 라이브러리나 프레임워크를 사용하길 기대하고 있습니다. (=maintainer들아 일해라)
몇몇 라이브러리에서는 data fetching을 위한 Suspense
를 사용할 수 있습니다. 즉각적인 data fetching에서 Suspense를 사용하는 것이 기술적으로는 가능하지만, 보편적인 방식으로는 권장하지 않습니다.
Reading the data
data를 읽을 때마다 로딩상태에서 무엇을 보여줄 지 결정해야함Spectify the loading state
한 페이지 내에 두 컴포넌트가 로딩 상태를 가질 때 이 두 컴포넌트의 로딩 상태를 합치고 싶은 경우
두 문제를 해결하기 위해 suspense를 사용
추후에는 특정 프레임워크가 아니더라도 suspense를 사용할 수 있도록 하는게 목표입니다. 가장 suspense가 잘 동작하는 방법은 본인의 어플리케이션 아키텍쳐에 깊에 통합되는 것입니다.
이전 버전에서는 suspense가 React.lazy와 함께 클라이언트에서 코드를 분리하는 역할로 사용되었지만, 코드를 기다리는 것 너머의 역할이 될 것입니다. (선언된 같은 suspense fallback에서 비동기적 오퍼레이션들을 다룰 수 있도록 - data fetching만을 위한게 아닌 비동기적인 이미지를 위한 fallback이라던가..)
(image from React 18 conf keynote)
Server component
는 서버와 클라이언트를 걸쳐 어플리케이션을 개발할 수 있도록 해주는 기능입니다.Concurrent rendering과 기술적으로 연관되어있지는 않지만, 함께 사용했을 때 더욱 잘 동작할 수 있도록 설계되었습니다.
(현재 Next에서 app directory와 함께 사용 가능 - 사용 가능한 프레임워크들은 여기에 업데이트될 예정)
기존 방식의 경우 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);
})
// 딱 끝나는 시점에서 렌더
}
transition은 급한(urgent) 업데이트와 급하지 않은(non-urgent)를 구분하기 위한 개념입니다.
예를 들어, 필터버튼을 클릭했을 때,
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.Suspense를 사용하면 컴포넌트 트리의 일부가 아직 표시될 준비가 되지 않은 경우 로딩 상태를 선언적으로 지정할 수 있습니다:
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
Suspense는 UI의 로딩상태를 일급 선언적 개념으로 만들어, 그 위에 고차원적인 기능을 구축할 수 있게 합니다. 추후 기능은 더 확장될 예정이며, transition과 함께 사용시 충분한 데이터를 받기 전까지 렌더링을 미뤄, 안좋은 로딩 상태가 보여지는 것을 막을 수 있게됩니다.
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
에서 export되는 API들
renderToPipeableStream
: Node 환경에서 스트리밍용renderToReadableStream
: edge 환경에서 스트리밍용 (Deno, Cloudflare)기존에 있던 renderToString
도 동작하지만 권장되지는 않습니다.
리액트에서 상태를 유지하며 세션별 UI를 더하거나 뺄 수 있도록 개발될 예정입니다.이게 동작하기 위해선 같은 컴포넌트 상태를 unmount하고 remount할 때 사용해야 합니다. 이에 컴포넌트는 탄력적으로 대응할 수 있어야합니다. 이 문제 해결을 위해 Strict Mode에서 모든 컴포넌트가 첫번째 렌더 이후 두번째로 이전 상태를 복구해 자동으로 unmount되고 remount 되는 옵션을 추가했습니다.
서버와 클라이언트에서 모두 유니크한 아이디를 만들어줄 수 있는 훅입니다. 서버와 클라이언트간 hydration 불일치를 해결해줍니다. 데이터 key로의 사용은 권장되지 않습니다.
업데이트가 급하지 않은 상태임을 표시하는 훅입니다. 해당 훅이 사용되지 않는 업데이트는 모두 urgent update로 간주됩니다.
non-urgent 업데이트의 리렌더링을 미루는 훅입니다. 디바운싱과 비슷하지만, 고정된 딜레이 시간 없습니다.
외부 스토어가 스토어 업데이트를 강제로 동기화하여 동시 읽기를 지원할 수 있는 훅입니다. 외부 데이터를 얻을 때, useEffect를 사용하지 않아도 됩니다. 해당 코드는 application의 직접적인 사용보단, 라이브러리에서의 사용을 위해 개발되었습니다.
CSS-in-js 라이브러리의 렌더링에서 스타일 주입의 성능 문제 해결을 위한 훅입니다. DOM mutaion 이후, layout effect가 새 layout을 읽기 전에 동작합니다. 이미 CSS-in-js를 구축해두었다면 사용을 권장하지 않습니다.
깔끔한 정리 감사합니다