Moon Work

React Suspense 적용기 본문

React

React Suspense 적용기

moonkey 2022. 12. 11. 20:13

시작하며

React에서 비동기를 사용할 경우 기본 hook을 제외하고는 아래와 같은 방식으로 컴포넌트가 작성되곤 한다.

import { useEffect, useState } from 'react';
import { User } from '../types/user';

const UsersNoQuery = () => {
    const [users, setUsers] = useState<User[]>([]);
    const [loading, setLoading] = useState<boolean>(true);
    const [error, setError] = useState<boolean>(false);

    useEffect(()=>{
        (async ()=> {
            try{
                const response = await fetch('https://jsonplaceholder.typicode.com/users').then(res=>res.json())
                setUsers(response);
            }catch(e){
                console.log(e);
                setError(true);
            }finally{
                setLoading(false);
            }
        })();
    },[]);

    return (
        <>
        {
            loading ?
            <h1>loading...</h1>
            :
            users &&
            <ul>{
                users.map((user,index)=>{
                    return <li key={index}> user... </li>
                })
            }</ul>
        }
        </>
    )
}

export default UsersNoQuery;

query를 사용하면 비동기 부분이 조금 더 단순해지긴 하지만 항상 렌더링 부분에 if문이 들어가는 것에서 더 나은 가독성을 가진 방법이 있지 않을까 하는 의문이 들었다. 또한 비동기 요청을 하는 부분과 조건에 따라 다른 렌더를 하는 부분이 같은 컴포넌트 안에 있는 것이 유연성과 재사용성을 안좋게 만든 다는 생각이 들었다. 여기서 React 18에서 나온 Suspense를 통해 보다 나은 방식으로 바꿀 수 있었다.

 

React Suspense

  • Suspense를 사용하게 되면 REST API나 GraphQL 등 비동기로 데이터를 가져오는 동안 다른 컴포넌트를 보여준다. 
  • <Suspense>로 컴포넌트를 감싸줄 경우 해당 컴포넌트의 렌더링을 특적 작업(비동기 등) 이후로 미루고 그 작업이 완수될 때까지 falback에 담긴 component를 대신 보여줄 수 있다.  
  • 과정을 중점에 두는 명령형 프로그래밍이 아닌  결과와 가독성에 중점을 둔 선언형 프로그래밍을 강화한다. 
  • 컴포넌트가 먼저 렌더된 뒤 나중에 데이터를 받으면서 생기는 waterfall 현상이 데이터를 받은 뒤 렌더 되는 과정을 통해 방지된다.

 

Suspense 적용하기

Suspense를 적용하게 될 경우 아래와 같이 컴포넌트를 분리해서 page component, container component, item component로 분리할 수 있었다. 

 

import { Suspense, useEffect, useState } from 'react';
import { User } from '../types/user';

const UserListPage = () => {
    return (
        <Suspense fallback={<h1>Load user...</h1>}>
            <UserListContainer />
        </Suspense>
    )
}
const UserListContainer = () =>{
    const [users,setUsers] = useState<User[]>([]);

    const fetchUsers = async () =>{
        const response = await fetch('https://jsonplaceholder.typicode.com/users')
        .then(res=> res.json());
        setUsers(response);
    }
    
    useEffect(()=>{
        fetchUsers();
    },[]);

    return (
        <ul>{
            users.map(user=>{
                return <UserItem key={user.id} user={user} />
            })
        }</ul>
    )
}

const UserItem = (user:any) =>{
    return <li>{user.user.name}</li>
}


export default UserListPage;

 

Suspense + react-query

react-query를 사용하면 비동기 작업을 보다 가독성 좋게 표현할 수 있다. query의 option에 suspense:true를 통해 suspense에 데이터를 정상적으로 받아온 것을 전달 할 수 있다. 최종적으로 react-query와 suspense를 사용을 통해 보다 component의 재사용과 확장성을 높일 수 있었고 더 나은 가독성을 가진 코드를 작성할 수 있었다. 

import { Suspense } from 'react';
import { useQuery } from 'react-query';
import { fetchUsers } from '../api/fetchUser';
import { User } from '../types/user';

const UserListPage = () => {
    return (
        <Suspense fallback={<h1>Load User...</h1>}>
            <UserListContainer />
        </Suspense>
    )
}
const UserListContainer = () =>{
    const {data:users} = useQuery(['productsSuspense'], fetchUsers, {
        suspense: true,
    })

    return (
        <ul>{
             users.map((user:User)=>{
                return <UserItem key={user.id} user={user} />
            })
        }</ul>
    )
}

type UserItemProps = {
    user:User
}
const UserItem = ({user}:UserItemProps) =>{
    return <li>{user.name}</li>
}


export default UserListPage;

 

아직 suspense를 완전히 이해한 것은 아니지만 사용하면서 skeleton ui가 필요할 경우나 그 외 활용 방법에 대해 더 공부가 필요해보인다.