리액트 v18.0

React
2024-04-18

원글 : https://react.dev/blog/2022/03/29/react-v18

Concurrent React

Concurrent React는 리액트의 핵심 렌더링 모델에 대한 근본적인 업데이트이다.

Concurrent React의 주요 속성은 렌더링이 중단 가능하다는 것입니다.

동기식 렌더링을 사용하면 업데이트가 렌더링을 시작하면 사용자 화면에서 결과를 볼 수 있을 때 까지 아무것도 중단할 수 없다. 하지만 동시 렌더링에서는 항상 그런 것은 아니다. 리액트는 업데이트 렌더링을 시작하고 중간에 일시 중지한 다음 나중에 계속할 수 있다. 진행 중인 렌더링을 완전히 포기할 수도 있다.

리액트는 렌더링이 중단되더라도 UI가 일관되게 표시되도록 보장한다.

이를 위해 전체 트리가 평가된 후 끝까지 DOM mutations 수행을 기다린다. 이 기능을 통해 리액트는 메인 스레드를 차단하지 않고 백그라운드에서 새 화면을 준비할 수 있다. 이는 UI가 대규모 렌더링 작업 중에도 사용자 입력에 즉시 응답하여 유동적인 사용자 경험을 생성할 수 있음을 의미한다.

또 다른 예는 재사용 가능한 상태이다. Concurrent React는 화면에서 UI 섹션을 제거한 다음 이전 상태를 재사용하면서 나중에 다시 추가할 수 있다. 예를 들어, 사용자가 화면에서 탭을 쳤다가 다시 돌아오면 리액트는 이전 화면을 이전과 동일한 상태로 복원할 수 있어야 한다.

동시 렌더링은 리액트의 강력한 새 도구이며 Suspense, 전환 및 스트리밍 서버 렌더링을 포함하여 대부분의 새로운 기능은 이를 활용하도록 구축되었다.

React v18로 업그레이드하면 동시 기능을 즉시 사용할 수 있다. 예를 들어, startTransition을 사용하면 사용자 입력을 차단하지 않고 화면 간을 이동할 수 있다. 또는 DeferredValue를 사용하여 비용이 많이 드는 재렌더링을 제한할 수도 있다.


새로운 기능 : Automatic Batching

일괄 처리(Automatic Batching)는 리액트가 더 나은 성능을 위해 여러 상태 업데이트를 단일 리렌더링으로 그룹화하는 것이다. React 17 이전까지는 리액트 이벤트 핸들러 중에만 업데이트를 일괄 처리했지만 Promise, setTimeout, 기본 이벤트 핸들러 또는 기타 이벤트 내부의 업데이트는 기본적으로 리액트에서 일괄 처리하지 않았다.

이벤트 핸들러 중에서 일괄 처리

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> ); }
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> ); }

React 17 이전의 프로미스에서 처리

function App() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); function handleClick() { fetchSomething().then(() => { // React 17 이전 : 일괄 처리되지 않는다. setCount(c => c + 1); // 리렌더링을 유발한다. setFlag(f => !f); // 리렌더링을 유발한다. }); } 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() { fetchSomething().then(() => { // React 17 이전 : 일괄 처리되지 않는다. setCount(c => c + 1); // 리렌더링을 유발한다. setFlag(f => !f); // 리렌더링을 유발한다. }); } return ( <div> <button onClick={handleClick}>Next</button> <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1> </div> ); }

React 18부터 모든 업데이트는 출처에 관계없이 자동으로 일괄 처리된다.

이는 Promise, setTimeout, 기본 이벤트 핸들러 또는 기타 이벤트 내부의 업데이트가 리액트 이벤트 내부의 업데이트와 동일한 방식으로 일괄 처리된다는 것을 의미한다. 이로 인해 렌더링 작업이 줄어들어 애플리케이션 성능이 향상될 것으로 예상된다

React 18 이후의 일괄 처리

function App() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); function handleClick() { fetchSomething().then(() => { // React 18에서는 이들을 일괄처리한다. setCount(c => c + 1); setFlag(f => !f); // 리액트는 마지막에 한 번만 리렌더링한다. (일괄 처리) }); } 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() { fetchSomething().then(() => { // React 18에서는 이들을 일괄처리한다. setCount(c => c + 1); setFlag(f => !f); // 리액트는 마지막에 한 번만 리렌더링한다. (일괄 처리) }); } return ( <div> <button onClick={handleClick}>Next</button> <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1> </div> ); }

새로운 기능 : Transition

전환(Transition)은 긴급 업데이트와 긴급하지 않은 업데이트를 구별하기 위한 리액트의 새로운 개념이다.

  • 긴급 업데이트는 입력, 클릭, 누르기 등 과같은 직접적인 상호 작용을 반영한다.
  • 전환 업데이트는 UI를 한 보기에서 다른 보기로 전환한다.

입력, 클릭, 누르기와 같은 긴급 업데이트에는 물리적 개체의 동작 방식에 대한 우리의 직관과 일치하도록 즉각적인 응답이 필요하다. 그렇지 않으면 유저는 “틀렸다”고 느낀다.

일반적으로 최상의 사용자 경험을 위해서는 단일 사용자 입력으로 긴급 업데이트와 긴급하지 않은 업데이트가 모두 발생해야 한다. 입력 이벤트 내에서 startTransition API를 사용하여 리액트에게 어떤 업데이트가 긴급하고 "전환"인지 알릴 수 있다.

import { startTransition } from 'react'; // 긴급: 무엇을 입력했는지 보여준다. setInputValue(input); // 내부의 모든 상태 업데이트를 전환으로 표시한다. startTransition(() => { // 전환: 결과를 보여준다. setSearchQuery(input); });
import { startTransition } from 'react'; // 긴급: 무엇을 입력했는지 보여준다. setInputValue(input); // 내부의 모든 상태 업데이트를 전환으로 표시한다. startTransition(() => { // 전환: 결과를 보여준다. setSearchQuery(input); });

startTransition에 래핑된 업데이트는 긴급하지 않은 업데이트로 처리되며 클릭이나 키 누르기와 같은 더 긴급한 업데이트가 들어오면 중단된다. 전환이 사용자에 의해 중단되면(예: 여러 문자를 연속으로 입력하여) 리액트는 오류를 발생시킨다. 완료되지 않은 오래된 렌더링 작업을 제거하고 최신 업데이트만 렌더링한다.

  • useTransition: 보류 상태를 추적하는 값을 포함하여 전환을 시작하는 hooks이다.
  • startTransition: hooks을 사용할 수 없을 때 전환을 시작하는 방법이다.

전환은 업데이트가 중단될 수 있도록 동시 렌더링을 선택한다. 콘텐츠가 다시 일시 중지되면 전환은 리액트에게 백그라운드에서 전환 콘텐츠를 렌더링하는 동안 현재 콘텐츠를 계속 표시하도록 지시한다.


새로운 서스펜스 기능

Suspense를 사용하면 아직 표시할 준비가 되지 않은 컴포넌트 트리의 일부에 대한 로드 상태를 선언적으로 지정할 수 있다.

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

Suspense는 "UI 로딩 상태"를 React 프로그래밍 모델의 일류 선언적 개념으로 만든다. 이를 통해 그 위에 더 높은 수준의 기능을 구축할 수 있다.

우리는 몇 년 전에 한정판 Suspense를 출시했다. 그러나 지원되는 유일한 사용 사례는 React.lazy를 사용한 코드 분할이었고 서버에서 렌더링할 때는 전혀 지원되지 않았다.

React 18에서는 서버에 Suspense에 대한 지원을 추가하고 동시 렌더링 기능을 사용하여 기능을 확장했다.

React 18의 Suspense는 전환 API와 결합될 때 가장 잘 작동한다. 전환 중에 일시 중지하면 리액트는 이미 표시된 콘텐츠가 fallback으로 대체되는 것을 방지한다. 대신 리액트는 잘못된 로딩 상태를 방지하기 위해 충분한 데이터가 로드될 때까지 렌더링을 지연한다.


새로운 hooks

useId : 하이드레이션 불일치를 방지하면서 클라이언트와 서버 모두에서 고유 ID를 생성하기 위한 hooks이다. 새로운 스트리밍 서버 렌더러가 HTML을 잘못된 순서로 전달하는 방식 때문에 React 18에서는 더욱 중요하다.

useTransition startTransition : 일부 상태 업데이트를 긴급하지 않은 것으로 표시할 수 있다. 기타 상태 업데이트는 기본적으로 긴급 업데이트로 간주된다. 리액트는 긴급하지 않은 상태 업데이트(예: 검색 결과 목록 렌더링)를 중단하기 위해 긴급 상태 업데이트(예: 텍스트 입력 업데이트)를 허용한다.

useDeferredValue : 트리에서 긴급하지 않은 부분의 리렌더링을 연기할 수 있다. 디바운싱과 유사하지만 이에 비해 몇 가지 장점이 있다. 고정된 시간 지연이 없으므로 리액트는 첫 번째 렌더링이 화면에 반영된 직후 지연된 렌더링을 시도한다. 지연된 렌더링은 중단 가능하며 사용자 입력을 차단하지 않는다.

도로모의 기술 블로그

# Contact : jyw966@naver.com

Copyright © doromo. All Rights Reserved.