리액트 17과 18의 변경 사항 살펴보기 (2)

keemsebeen·2024년 12월 20일

모던 리액트 Deep Dive

목록 보기
16/18

10.2 리액트 18 버전 살펴보기

새로 추가된 훅 살펴보기

useId
컴포넌트별로 유니크한 값을 생성하는 새로운 훅이다. 컴포넌트 트리에서 컴포넌트가 가지는 모든 값이 겹치지 않으면서도, SSR환경에서 하이드레이션이 일어날때 서버와 클라이언트에서 동일한 값을 가져야 에러가 나지 않기 때문에 이점도 고려해야한다.

서비스에서 컴포넌트별로 고유한 값을 사용해야 한다면 반드시 하이드레이션을 고려해야 하기 때문에 까다로운 작업이였다. 그러나 useId를 사용하면 클라이언트와 서버에서 불일치를 피하면서 컴포넌트 내부의 고유한 값을 생성할 수 있다.

같은 컴포넌트임에도 서로 인스턴스가 다르기 때문에 다른 랜덤한 값을 만들어낸다. 또한 클라이언트와 서버 간에 동일한 값이 생성되어 하이드레이션 이슈도 발생하지 않는다.

useTransition
UI변경을 가로막지 않고 상태를 업데이트할 수 있는 리액트 훅이다.
상태 업데이트를 긴급하지 않은 것으로 간주해 무거운 렌더링 작업을 조금 미루고, 사용자에게 더 나은 경험을 제공할 수 있다.

극단적으로 많은 양의 데이터(A 컴포넌트)와, 일반적인 데이터를 다루는 컴포넌트(B 컴포넌트)가 존재할때 사용이 가능하다. 사용자가 A컴포넌트를 렌더링하다 바로 B컴포넌트를 클릭하여 바꾸게 되면 A컴포넌트의 렌더링이 끝난 후 B컴포넌트 렌더링을 시작한다. 이런 경우 렌더링 작업이 오래걸려 UI 렌더링이 가로 막힌다.

이경우 A를 잘못 눌렀을 수도 있기 때문에 A렌더링을 중단하고 B를 보여주는 것이 더 적절하다. useTransition은 이러한 경우에 사용된다.

비동기처럼 작동하며, 동시성을 다룰 수 있는 새로운 훅이다. 과거 리액트의 모든 렌더링은 동기적으로 작동해 느린 렌더링 작업이 있을 경우 애플리케이션 전체적으로 영향을 끼쳤지만 지금 진행 중인 렌더링을 버리고 새로운 상태값으로 다시 렌더링하는 등의 작업을 할 수 있다.

❗️주의사항❗️

  • startTransition 내부는 반드시 setState와 같은 상태를 업데이트하는 함수와 관련된 작업만 넘길 수 있다.
  • startTransition으로 넘겨주는 상태 업데이트는 다른 모든 동기 상태 업데이트로인해 실행이 지연될 수 있다. 예를 들어 타이핑으로 인해 setState가 일어나는 경우 타이핑이 끝날때까지 useTransition으로 지연시킨 상태 업데이트는 일어나지 않는다.
  • startTransition으로 넘겨주는 함수는 반드시 동기함수여야 한다.
export default function Tab() {
	const [isPending,startTransition] = useTransition();
	const [tab,setTab] = useState<Tab>('about');
	
	function selectTab(nextTab:Tab){
		startTransition(() => {
			setTab(nextTab);
		})
	}
}

useDeferredValue
리액트 컴포넌트 트리에서 리렌더링이 급하지 않은 부분을 지연할 수 있게 도와주는 훅이다. 다른 훅들과 비슷하지만 useDeferredValue만이 가진 장점이 몇가지 존재한다.

디바운스useDeferredValue
지연시간고정첫번째 렌더링 완료 후 지연된 렌더링 수행

useTransitionuseDeferredValue
statestate값을 업데이트 하는 함수를 감싸서 사용state 값 자체만을 감싸서 사용
적절한 사용 경우낮은 우선순위로 처리해야 할 작업에 대해 직접적으로 상태를 업데이트 가능한 경우props에 관여하지 못하고 오로지 값만 받아야 하는 경우

useTransition, useDeferredValue 둘다 지연된 렌더링을 한다는 점에서는 모두 동일한 역할을 하는 것을 알 수 있다.

useSyncExternalStore
테어링
하나의 state 값이 있음에도 서로 다른 값을 기준으로 렌더링되는 현상이다. 리액트 18에 들어서면서 렌더링을 일시중시하거나 뒤로 미루는 등의 최적화가 가능해지면서 동시성 이슈가 발생할 수 있다.

  1. 첫번째 컴포넌트에서 외부 데이터 스토어의 값이 파란색이었으므로 파란색을 렌더링했다.
  2. 나머지 컴포넌트들도 파란색으로 렌더링을 준비하고 있었다.
  3. 갑자기 외부 데이터 스토어의 값이 빨간색으로 변경됐다.
  4. 나머지 컴포넌트들은 렌더링 도중에 바뀐 색을 확인해 빨간색으로 렌더링했다.
  5. 결과적으로 같은 데이터 소스를 바라보고 있음에도 컴포넌트의 색상이 달라지는 테어링현상이 발생했다.

과거에는 동기적으로 렌더링이 일어났기에 문제가 없었지만, 리액트에서 관리할 수 없는 외부 데이터 소스에 의존한다면 문제가 생기게된다. 이 문제를 해결하기 위해 useSyncExternalStore이 등장했다.

애플리케이션 코드에 직접적으로 사용할 일은 많지 않지만 사용 중인 관리 라이브러리가 외부에서 상태를 관리하고 있다면 이 useSyncExternalStore를 통해 외부 데이터 소스의 변경을추적하고 있는지 반드시 확인해야 한다.

useInsertionEffect
CSS-in-js라이브러리를 위한 훅이다. useInsertionEffect는 DOM이 실제로 변경되기 전에 동기적으로 실행된다. 내부에 스타일을 삽입하는 코드를 집어넣음으로써 브라우저가 레이아웃을 계산하기 전에 실행될 수 있게끔 해서 좀 더 자연스러운 스타일 삽입이 가능하다.

function Index(){
	useEffect(() => {
		console.log('useEffect') // 3
	})
	useLayoutEffect(() => {
		console.log('useLayoutEffect') // 2
	})
  useInsertionEffect(() => {
		console.log('useInsertionEffect') // 1
	})
}

react-dom/client

리액트 트리를 만들 때 사용되는 API가 변경됐다.
createRoot
기존 react-dom에 있던 render 메소드를 대체할 새로운 메소드이다.
hydrateRoot
서버 사이드 렌더링 애플리케이션에서 하이드레이션을 하기 위한 새로운 메소드이다.

react-dom/server

renderToPipeableStream
리액트 컴포넌트를 HTML로 렌더링하는 메소드다. 스트림을 지원하는 메소드로, HTML을 점진적으로 렌더링하고 클라이언트에서는 중간에 script를 삽입하는 등의 작업을 할 수 있다. 이를 통해 서버에서는 Suspense를 사용해 빠르게 렌더링이 필요한 부분을 먼저 렌더링할 수 있고, 값비싼 연산으로 구성된 부분은 이후에 렌더링되게끔 할 수 있다.

hydrateRoot를 호출하면 서버에서는 HTML을 렌더링하고, 클라이언트의 리액트에서는 여기에 이벤트만 추가함으로써 첫번째 로딩을 매우 빠르게 할 수 있다.

renderToReadableStream
서버 환경이 아닌 클라우드플레어나 디노 같은 웹 스트림을 사용하는 모던 엣지 런타임 환경에서 사용되는 메소드다.

자동 배치

여러 상태 업데이트를 하나의 리렌더링으로 묶어서 성능을 향상시키는 방법이다.

Promise, sleep과 같은 함수를 사용해 고의로 실행을 지연시키지 않으면 버전과 상관없이 동일하게 한 번만 렌더링 된다. 17 이하의 과거 버전의 경우 비동기 이벤트에는 자동 배치가 이뤄지지 않는 것이다.

다음과 같은 동기/비동기 배치 작업에 일관성이 없다는 문제점도 존재했다.리액트 18에서는 이를 보완하여 루트 컴포넌트를 createRoot를 사용해 만들면서 모든 업데이트가 배치 작업으로 최적화할 수 있게 됐다.

더욱 엄격해진 엄격 모드

리액트의 엄격모드
리액트 애플리케이션에서 발생할 수도 있는 잠재적인 버그를 찾는데 도움이 되는 컴포넌트다. 개발자 모드에서만 작동한다. 컴포넌트 형태로 사용할 수 있기 때문에 특정 컴포넌트 내부에서만 작동하게 할 수도 있다.

다음과 같은 작업들을 수행한다.

  • 더 이상 안전하지 않은 특정 생명주기를 사용하는 컴포넌트에 대한 경고
    • 클래스 컴포넌트에서 사용되는 생명주기 메서드 중 일부 사용 시
  • 문자열 ref 사용 금지
  • findDOMNode에 대한 경고 출력
  • 구 Context API 사용 시 발생하는 경고
  • 예상치 못한 부작용 검사
    • 클래스 컴포넌트의 setState의 첫번째 인수
    • 함수 컴포넌트의 body
    • useState, useMemo, useReducer에 전달되는 함수

리액트 18에서 추가된 엄격모드
컴포넌트가 마운트 해제된 상태에서도 컴포넌트 내부의 상태값을 유지할 수 있는 기능을 제공할 예정이라고 한다.

예를 들어 사용자가 뒤로가기를 했다가 다시 현재 화면으로 돌아왔을 때, 리액트가 즉시 이전의 상태를 그대로 유지해 표시할 준비를 하는 기능이다.

컴포넌트가 최초에 마운트될 때 자동으로 모든 컴포넌트를 마운트 해제하고 두 번째 마운트에서 이전 상태를 복원하게 된다. 이 기능은 오직 개발 모드에서만 적용 된다고 한다.

Suspense 기능 강화

이전에는 실험 버전으로 도입된 기능으로, 컴포넌트를 동적으로 가져올 수 있게 도와주는 기능이다.

lazy를 통해 컴포넌트를 지연 시키고, 컴포넌트를 렌더링하는데 사용됐다. 따라서 상대적으로 중요하지 않은 컴포넌트를 분할해 초기 렌더링 속도를 향상시키는데 많은 도움을 줬다.

이제는 실험 단계를 벗어나 정식으로 지원된다.

인터넷 익스플로러 지원 중단에 따른 추가 폴리필 필요

Promise, Symbol, Object.assign 을 지원하지 않는 브라우저에서 서비스를 해야한다면 이 세가지 기능을 위한 폴리필을 반드시 추가해야한다.

리액트뿐만 아니라 요즘 출시되는 대부분의 라이브러리가 ES5 지원을 중단하는 추세이기 때문에 만약 웹서비스가 여전히 익스를로러 11을 지원해야 한다면 폴리필 설치 및 트랜스파일에 신경 써야 한다.

리액트 18 버전 업의 핵심은 동시성 렌더링이다. 과거 렌더링 과정은 중간에 일시 중지 한다거나 렌더링 도중에 해당 렌더링 결과물을 포기한다든가 하는 메커니즘이 없었다. 그러나 리액트 18에서는 동시성 렌더링을 지원해 렌더링 중간에 일시 중지를 한다거나, 진행 중인 렌더링 작업을 포기하고 다시 시작할 수도 있다. 메인스레드를 차단하지 않음으로써, 사용자는 웹 애플리케이션에서 렌더링으로 인해 UI가 방해받는 현상을 겪지 않을 수 있고 반응성 또한 확보할 수 있게 된다고 한다!

profile
프론트엔드 공부 중인 김세빈입니다. 👩🏻‍💻

0개의 댓글