ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React 18 훑어보기
    공부라도 하자 2023. 10. 2. 15:38

    * 2023년 3월 20일에 개인 노션에 작성한 글을 옮겨온 것

    React 18

    동시성

    React 18에서는 가장 중요한 추가 기능인 동시성이 도입됩니다. 이로 인해 렌더링 엔진의 성능을 개선하고 사용자 경험을 향상시킬 수 있습니다.

    동시성은 동시에 여러 버전의 UI를 준비할 수 있도록 하는 백그라운드 메커니즘입니다. 이를 통해 렌더링이 중단 가능해졌습니다.

    💡 동시성이란?
    싱글 코어 환경에서 멀티 스레드 환경을 구성하여 Context Switching을 통해 동시에 실행되는 것처럼 보이게 하는 것. 병렬성은 멀티 코어에서 멀티 스레드 환경을 구성하여 여러 작업을 동시에 실행하는 것
    = 비슷하지만 다른 것으로 병렬성(Parallelism)이 있음
    여러 작업을 더 작은 단위로 나눈 뒤, 그들 간의 우선순위를 정하고 그에 따라 작업을 번갈아 수행하는 방법이다. 

    이전에는 대부분의 개발자들이 사용자 경험 개선을 위해 메모이제이션(memoization)이나 디바운스(debounce) 기법을 사용해왔습니다. 하지만 이러한 방법은 주된 문제 해결을 뒤로 미루는 것입니다.

    예를 들어 필터링을 이용한 검색 기능을 만든다면, 디바운스나 쓰로틀(throttle)을 이용하여 특정 최대 빈도수로 업데이트 하거나 사용자가 타이핑을 멈추고 나서만 목록을 업데이트하게 합니다. 하지만 성능이 좋지 않은 곳에서는 디바운스나 쓰로틀도 렌더링이 시작되면 중단할 수 없기 때문에 여전히 버벅일 것입니다.

     

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

    이전의 React 버전에서는 동일하게 업데이트가 중단되지 않는 단일 동기 트랜잭션으로 동작하여 렌더링을 시작하면 사용자가 화면에서 결과를 볼 수 있을 때까지 아무것도 업데이트를 중단할 수 없었습니다.

    동시 렌더링에서는 렌더링을 시작한 후 일시중지하고 다시 시작할 수도 있으며, 렌더링을 완전히 중단할 수도 있습니다. 특정 state가 변경되었을 때 현재 UI를 유지하고, 해당 변경에 따른 UI 업데이트를 동시에 준비합니다. 준비 중인 UI의 렌더링 단계가 특정 조건에 부합하면 실제 DOM에 반영됩니다.

    동시성은 여러 작업을 작은 단위로 나눈 뒤, 그들 간의 우선순위를 정하고 그에 따라 작업을 번갈아 수행하는 방법이다. 서로 다른 작업들이 실제로 동시에 수행되는 것은 아니지만, 작업 간의 전환이 매우 빠르게 이루어지면서 동시에 수행되는 것처럼 보이는 것이다. time-slicing

     

    React Concurrent Mode 간단 요약

    1. 메인 스레드를 블록하지 않는다.
    2. 동시에 여러 작업들을 처리하고 우선 순위에 따라 각 작업들 간에 전환할 수 있다.
    3. 최종 결과로 확정하지 않고도 부분적으로 트리를 렌더링 할 수 있다.

    react를 사용하는 방식은 이전과 같습니다. prop 및 state와 같은 개념은 근본적으로 동일하게 작동하며, 휴리스틱을 사용하여 업데이트의 급한 정도만 결정하고 몇줄의 코드를 수정해서 사용자가 모든 상호작용에 대해 원하는 사용자의 경험을 얻을 수 있도록 합니다.

    render → createRoot

    const root= ReactDOM.createRoot(
    	document.getElementById("root") as HTMLElement
    );

     

    Automatic Batching

    렌더링 최적화를 위한 새로운 업데이트입니다.

    이전에는 이벤트 핸들러에서 자동 배칭을 수행해왔으나, 이벤트 핸들러 밖에서 발생하는 업데이트는 배칭되지 않아 일관성이 없었습니다.

    리액트에서 배칭은 여러 개의 state 업데이트를 하나의 리렌더링으로 묶는 것을 의미합니다.

    function App() {
      const [count, setCount] = useState(0);
      const [flag, setFlag] = useState(false);
    
      function handleClick() {
        setCount((c) => c + 1); // 아직 리렌더링 하지 않는다.
        setFlag((f) => !f); // 아직 리렌더링 하지 않는다.
        // React는 이 함수가 끝나면 리렌더링을 한다.(이것이 배칭이다!)
      }
    }
    
    
    function handleClick() {
      console.log("=== click ===");
      fetchSomething().then(() => {
        // 리액트 17과 이전 버전에서는 이 업데이트들이 이벤트가 완료된 후의 콜백에서 실행되기 때문에 배칭되지 않았다.
        setCount((c) => c + 1); // 리렌더링을 발생시킨다
        setFlag((f) => !f); // 리렌더링을 발생시킨다.
      });
    }
    
    

    하지만 18버전에서는 모든 업데이트들이 어디서 왔는지와 상관없이 자동으로 배치됩니다.

    배치를 원하지 않을 때에는 flushSync 함수를 사용하여 배칭을 피할 수 있습니다.

    import { flushSync } from 'react-dom'; // Note: react-dom, not react
    
    function handleClick() {
      flushSync(() => {
        setCounter(c => c + 1);
      });
      // React has updated the DOM by now
      flushSync(() => {
        setFlag(f => !f);
      });
      // React has updated the DOM by now
    }
    
    

     

    Transition

    긴급하지 않은 UI 업데이트를 표시할 때 Transition을 사용할 수 있습니다. 즉시 업데이트되지 않아도 되는 UI 업데이트를 Transition이라고 합니다. 우선순위가 낮은 업데이트를 transition으로 표시해주면 React는 렌더링을 더 쉽게 최적화하고 업데이트의 우선 순위를 잘 매길 수 있게 됩니다.

    import { startTransition } from 'react';
    
    // 긴급한 것: 무엇을 타이핑하는지 보이게 하는 것
    setInputValue(input);
    
    // 이 state 업데이트가 긴급하지 않다고 표시
    startTransition(() => {
      // 긴급하지 않은 것: 결과를 보여주는 것
      setSearchQuery(input);
    });
    
    

     

    Suspense

    16.6에서 실험적인 기능으로 추가되었던 것이 이제는 정식 기능입니다.

    Suspense는 간단히 말해, 어떤 컴포넌트가 읽어야 하는 데이터가 아직 준비되지 않았다고 리액트에게 알려주는 새로운 매커니즘입니다. 이를 이용하여 컴포넌트 렌더링을 어떤 작업이 끝날 때까지 일시 중단하고, 다른 컴포넌트를 먼저 렌더링할 수 있습니다.

    suspense가 목표로 하는 코드는 간단합니다.

    성공한 경우에만 집중할 수 있는
    로딩 상태와 에러 상태가 분리된
    동기처럼 사용할 수 있는

    즉, 컴포넌트는 성공한 상태만을 다루고, 로딩 상태와 에러 상태는 외부에 위임함으로써 동기적인 코드와 큰 차이가 없는 코드를 만들겠다는 것입니다.

    <ErrorBoundary fallback={<MyErrorPage />}>
      <Suspense fallback={<Loader />}>
        <App />
      </Suspense>
    </ErrorBoundary>;

    컴포넌트를 '쓰는 쪽'에서 로딩 처리와 에러 처리를 합니다. 컴포넌트를 사용할 때 그 컴포넌트를 위 코드처럼 Suspense로 감싸주면, 컴포넌트의 렌더링을 특정 작업 이후로 미루고, 그 작업이 끝날 때까지는 fallback 속성으로 넘긴 컴포넌트를 대신 보여줄 수 있습니다. 에러 상태는 가장 가까운 ErrorBoundary가 componentDidCatch()로 처리합니다. async await가 try- catch문으로 따로 에러처리를 해주는 것과 유사한 방식입니다.

    async와 await를 사용할 때 모든 함수에 try ... catch를 감싸지 않는 것처럼, Suspense를 일으키는 모든 컴포넌트에 Suspense나 ErrorBoundary를 붙여주기 보다는 적당한 부분 단위로 에러와 로딩 상태를 한 번에 처리하게 됩니다.

    SWR이나 React Query를 사용하신다면, {suspense: true} 라는 옵션을 사용하기만 하면 자동으로 컴포넌트의 Suspense 상태가 관리됩니다. (Recoil을 사용하실 경우, async selector를 사용하시면 됩니다.)

    이처럼 Suspense를 사용하면 복잡한 비동기 처리를 간단하게 바꿀 수 있습니다. 뿐만 아니라 데이터가 준비되는 대로 하나씩 자연스럽게 보여주기 때문에 사용자 경험도 개선할 수 있습니다.

     

    참고 자료

    https://lss3070.github.io/2022/03/30/React-React-18-Update/

     

    [React] React 18버전에서의 바뀐점 - 19호실 | 섭섭이

    개요

    lss3070.github.io

    https://velog.io/@perfumellim/%EC%89%BD%EA%B2%8C-%EB%96%A0%EB%A8%B9%EC%97%AC%EC%A3%BC%EB%8A%94-React-18-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8

     

    쉽게 떠먹여주는 React 18 업데이트

    이 포스트는 React18 이후 React가 어떻게 달라졌는지, 이 변화가 어떤 걸 의미하는지 정리하는 글입니다.

    velog.io

     

Designed by Tistory.