React Suspense에 대해서 알아보자

React Suspense에 대해서 알아보자

React Suspense 기본 개념

Suspense는 React에서 비동기 작업을 처리할 때, 컴포넌트의 렌더링을 잠시 멈추고, 그 작업이 완료될 때까지 대기하는 기능을 제공해주는 도구이다. 쉽게 말해서, 데이터가 준비될 때까지 기다리게 하고, 그동안 로딩 상태를 보여준다.


주요 개념

  1. Suspense 컴포넌트:

    • Suspense 컴포넌트는 비동기 작업이 완료될 때까지 대기 상태를 관리한다.
    • fallback 속성을 사용해서 비동기 작업이 완료될 때까지 보여줄 UI를 정의할 수 있다.
    import React, { Suspense } from 'react';
    
    const MyComponent = React.lazy(() => import('./MyComponent'));
    
    function App() {
      return (
        <Suspense fallback={<div>Loading...</div>}>
          <MyComponent />
        </Suspense>
      );
    }
    
    export default App;
    
  2. React.lazy:

    • React.lazy를 사용하면 컴포넌트를 동적으로 로드할 수 있다.
    • React.lazy는 보통 코드 스플리팅을 위해서 사용된다.
    const MyComponent = React.lazy(() => import('./MyComponent'));
    
  3. API Fetch:

    • React Query의 suspense 옵션을 사용하면 간단하게 API가 응답되기 전에 Suspense의 fallback이 실행되도록 만들 수 있다.


응용 해보기

어떻게 동작하는 걸까?

기본적으로 컴포넌트에서 Promise가 Throw되면 Suspense는 fallback의 컴포넌트를 렌더링 한다. 아래는 기본적인 구조를 나타낸 것이지만 기본적으로 리액트에서 컴포넌트는 계속해서 실행되는 개념이기 때문에 Promise가 반복되서 실행되지 않도록 해야한다.

// Don't
function LazyComponent() {
    const data = fetchSomething(); // 'data' type is inferred to be Promise.

    if (data instanceof Promise) {
        throw data;
    }

    return (
        <div>
            {data}
        </div>
    )
}
메모라이즈

기본적으로는 메모리에 저장해두고 같은 키를 바탕으로 같은 객체를 반환하도록 해준다. Promise 타입일 때는 Promise를 Throw 시켜서 Suspense의 fallback을 실행시키는 전략이다.

const cache = new Map<string, unknown>()

function useMemorizedSuspensePromise<T>(keys: string[], fn: () => Promise<T>): T | null {
    const key = keys.join('.')

    if (!cache.has(key)) {
        const promise = fn().then((data) => {
            cache.set(key, data);
        });
        cache.set(key, promise);
    }

    const cachedData = cache.get(key);
    if (cachedData instanceof Promise) {
        throw cachedData;
    }

    return cachedData as T;
}

function LazyComponent() {
    const data = useMemorizedSuspensePromise(['fetch', 'something'], () => fetchSomething());

    return (
        <div>
            {data}
        </div>
    )
}
프로미스 상태 판별

프로미스의 처리 상태를 직관적으로 파악하기 위해서 간단한 함수를 만들어보자.

function createSuspensePromise<T>(promise: Promise<T>) {
    let status = 'pending';
    let result: T;
    const suspender = promise.then(
        r => {
            status = 'success';
            result = r;
        },
        e => {
            status = 'error';
            result = e;
        }
    );

    return {
        read() {
            if (status === 'pending') {
                throw suspender;
            } else if (status === 'error') {
                throw result;
            } else if (status === 'success') {
                return result;
            }
        }
    };
}

위 함수를 이용한 기본적인 사용 방법은 아래와 같다. 컴포넌트에서는 read() 함수만 실행하면 데이터를 가져오거나 Suspense 컴포넌트의 fallback이 실행된다.

const suspensePromise = createSuspensePromise(fetchSomething())

function LazyComponent() {
    const data = suspensePromise.read()

    return (
        <div>
            {data}
        </div>
    )
}

컴포넌트 내부에서 훅처럼 사용하도록 개선해보자.

function useSuspensePromise<T>(promise: Promise<T>, dependents: string[] = []) {
    const [resource, setResource] = useState<ReturnType<typeof suspensePromise<T>> | null>(null)

    useEffect(() => {
        setResource(suspensePromise(promise))
    }, dependents)

    return resource?.read();
}

function LazyComponent() {
    const data = useSuspensePromise(fetchSomething());

    return (
        <div>
            {data}
        </div>
    )
}

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.