React 18v 정리

Daon·2023년 3월 28일
1

정리

목록 보기
1/1
post-thumbnail

React18에서 업데이트 된 기능

React18의 가장 핵심 내용은 Concurrent Features(동시성 기능)이다.
이는 React가 추진하고있는 차기 핵심 기능인데 동시성 기능은 간단하게 싱글 스레드의 단점을 보완하는 솔루션이다.
Javascript는 싱글스레드로 UI 렌더링 도중에는 다른 작업이 진행되지 않기 때문에 DOM Tree가 거대한 경우 조작과 표현의 간극이 발행할 수 있다. 이를 개선하기 위해 동시성 기능을 추진하고 있다.

크게 3가지 기능으로 React 18을 소개 할 수 있다.

  • Automatic Batching
  • Transitions
  • Suspense Features
  • New Hooks (이부분은 3. 항목에서)
    • useId
    • useTransition
    • useDeferredValue
    • useSyncExternalStore
    • useInsertionEffect

자동배치(Automatic Batching)

여러개의 상태 업데이트를 하나의 리렌더링으로 그룹핑하는 것이다.

// Before: 배칭이 되지 않아서 상태 업데이트가 그룹핑 되지 않고 매번 리렌더링 된다
// (두번 리렌더링 된다)
setTimeout(() => {
  setCount((c) => c + 1);
  setFlag((f) => !f);
}, 1000);

// After: promise, setTimeout, native 이벤트 핸들러 등에서도 배칭이 된다.
// (한번만 리렌더링 된다)
setTimeout(() => {
  setCount((c) => c + 1);
  setFlag((f) => !f);
}, 1000);

만약 이러한 자동배치가 적용되지 않았으면 하는 경우는 ReactDOM.flushSync 함수를 사용할 수 있다.

flushSync(() => {
    setCount((c) => c + 1;
  });  // 리액트는 즉시 DOM을 업데이트 한다.

전환(Transitions)

[출처] https://yrnana.dev/post/2022-04-12-react-18/

위에 그림을 예시로 앨리스와 전화 도중 밥의 전화를 받도나서 앨리스와 통화가 가능하다
(Concurrent Features(동시성기능)을 이해하기 위해 좋은 예시이다.)

- 긴급한 업데이트 (urgent updates) : 직접적인 상호작용 반영 (타이핑, 호버, 스크롤 등). 업데이트가 즉각적으로 일어나는 대상.

- 전환 업데이트(Transition updates) : 하나의 뷰에서 다른 뷰로 UI 전환. 상태값 변화에 따른 모든 업데이트가 뷰에 즉시 반영되지 않아도 됨.

ex) 예시

[출처] https://abangpa1ace.tistory.com/252

중요한 점은, 전환 업데이트가 긴급 업데이트를 방해하면 안된다는 것이다.
React 17v 까지는 전환 업데이트를 set Timeout, throttle, debounce 등의 테크닉으로 우회하는 차선책을선택하였다.
React 18 부터는 이를 지원하는 Hooks들을 제공함으로써 전환 업데이트를 명시적으로 구분하여 상태 업데이트를 할 수 있다.

import { startTransition } from 'react';

// 긴급한 업데이트 : 입력하고 있는 값
setInputValue(input);

// startTransition으로 래핑된 업데이트는 긴급하지 않은 것으로 처리되고, 더 긴급한 업데이트가 들어오면 중단된다.
startTransition(() => {
  // 전환 업데이트: 입력값에 따른 쿼리값
  setSearchQuery(input);
});

startTransition

상태 업데이트를 전환 업데이트로 반영하는 메서드이다.
느린 렌더링 : 작업량이 많아 결과를 표현하는 UI 전환의 시간이 오래 걸리는 경우이다.
느린 네트워크 : 데이터 호출량이 많은 UI에 적용되며, Suspense와 연계된다.

Suspense

어플리케이션을 더 작은 독립적인 유닛으로 나누어서 별개의 렌더링을 거치는 것

React 18에서의 가장 큰 변화는 기존에 불가능했던 Suspense, Lazy Component의 SSR 에서의 사용이 가능해졌다는 것이다.
이 부분이 왜 가장 큰 변화냐면
기존 SSR의 로직은
1. 서버에서 UI를 그리기위해 데이터를 Fetching
2. 서버가 전체 어플리케이션을 HTML로 렌더링하고, 클라이언트로 Response를 보낸다
3. 클라이언트는 어플리케이션에 필요한 Javascript Bundle을 다운로드한다.
4. 클라이언트는 javascript 로직을 HTML에 연결하면서 끝난다.

여기서의 문제점은 각 단계가 끝나야 다음 단계로 시작할 수 있다는점이고
만약 어플리케이션이 거대해질 경우 한 부분의 렌더링만 지연되도 전체적인 페이지의 표현이 느려진다는 것이다.

그렇기에 React 18에서의 Suspense는 중요한 업데이트라고 볼 수 있다.

1. React.lazy - JS코드가 거대하고 복잡해서 로드 지연 해결

는 내부의 Lazy Component가 업로드되기 전에 앱을 선택적으로 Hydrate 해준다.

import { lazy } from 'react';

// import Comments from '@/views/components/Comments.js' - 기존
const Comments = lazy(() => import('./Comments.js'));

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

위에 이미지처럼 JS번들이 HTML 코드보다 일찍 로드된다면, 해당 컴포넌트를 기다리지 않고 나머지 영역을 먼저 hydrate할 것이다.

1-1 사용자 친화적 Hydration

복수의 Lazy Component가 Hydration 되는 경우 우선순위를 지정할 수 있는 기능이 추가되었다.

위에 이미지 같은경우 왼쪽 컴포넌트가 Hydration중이지만 사용자가 오른쪽 컴포넌트를 클릭할 경우

해당 컴포넌트의 우선순위가 높다고 판단하여
기존의 Hydration을 중단하고 상호작용한 컴포넌트를 우선 Hydration한다.
또한 이 때의 상호작용 이벤트를 기록해 두었다가, Hydration이 마무리되면 이를 다시 실행하여 컴포넌트가 반응하게 한다.

2. Data Fetching 지연 해결

또한 를 통한 Selective Hydration이 가능하다.
렌더링 비용이 큰 부분을 로 감싸 별도의 Hydration이 가능하다.

<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>

우측 하단의 렌더링이 상대적으로 느린 를 로 감싸서 선택적 Hydration을 적용했다.
또한, fallback 옵션에 대신 노출할 컴포넌트인 를 설정해서 해당 컴포넌트가 렌더링되기 전에 fallback 컴포넌트를 노출한다.

컴포넌트의 Data Fetching 전에는 fallback을, 이후에는 컴포넌트를 정상으로 노출한다.

React18에서 추가된 hook

  • New Hooks
    • useId
    • useTransition
    • useDeferredValue
    • useSyncExternalStore
    • useInsertionEffect

useId

client-server사이드에서 hydration 미스매치를 피하기 위해서 unique ID를 만들어주는 훅이다.
단, list의 key를 만들어주기 위한 훅이 아니니 그렇게 사용하지 말자.

useTransition

useTransition과 startTransition은 일부 상태 업데이트를 급하지 않은 업데이트로 간주한다.
concurrent에서는 급한 상태 업데이트가 급하지 않은 상태 업데이트를 중단할 수 있다.

useDeferredValue

급하지 않은 트리를 리렌더링 하는 것을 지연하게 해준다.
지연된 렌더링은 중단될 수 있고, 사용자의 입력을 방해하지 않는다.
디바운싱 / 쓰로틀링 기법과 유사하지만 timeout을 직접 지정할 필요 없이 리액트가 다른 급한 작업이 완료 되는 즉시
실행을 시킨다는 장점이 있다.

function Typeahead() {
  const query = useSearchQuery('');

  // useDeferredValue는 값만 지연하는 것이다.
  const deferredQuery = useDeferredValue(query);

  // 급한 update 동안 리렌더링을 방지하려면 컴포넌트는 메모해야 한다
  // useDeferredValue에만 통용되는 패턴은 아니며 디바운싱을 사용하는 경우도 마찬가지다
  const suggestions = useMemo(
    () => <SearchSuggestions query={deferredQuery} />,
    [deferredQuery],
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">{suggestions}</Suspense>
    </>
  );
}

useSyncExternalStore

라이브러리 개발을 위해 제공된 훅이다. (글로벌 상태관리)

useInsertionEffect

라이브러리 개발을 위해 제공된 훅이다. (CSS-in-JS)

출처
(https://abangpa1ace.tistory.com/252)
(https://yrnana.dev/post/2022-04-12-react-18/)
(https://doqtqu.tistory.com/348#2.1.%20useId)

개인 학습용으로 블로그 분들 내용을 종합하여 정리하였습니다.

profile
같이 일하고싶은 그런 개발자!

0개의 댓글