React Hooks가 나온 이후 Data Fetching을 좀 더 간결하게 하는 라이브러리가 많이 나오는 것 같습니다. 아래는 관련 라이브러리들을 npm trends에서 download 수를 비교해본 결과입니다.
최근 react-query와 SWR의 증가세가 돋보였습니다. 저는 기능을 조금 비교해본 후 SWR을 도입하기로 했습니다. SWR은 ZEIT에서 만든 Data Fetching 라이브러리인데 기본 소개와 사용법은 SWR 홈페이지에 잘 나와 있습니다.
그동안 CRUD를 개발하면서 사용자경험을 위해 세심하게 구현 코드를 늘려나갔는데요. Data Fetching 부분에 Redux 도 쓰고, 뒤로 가기 시 사용자 경험을 위해 axios에 cache도 하고 점점 복잡해져 갔습니다. 근데 SWR 라이브러리가 많은 것을 한 번에 해결해줬습니다.
SWR github 저장소에 axios와 함께 사용하는 예제가 있었는데 useRequest라는 커스텀 훅을 만들어서 사용합니다. 이 커스텀 훅은 axios 요청을 위한 옵션 객체를 stringify 하여 SWR 의 키로 사용하는 방식입니다.
저는 API Wrapper를 만들어두기를 선호해서 직접 axios로 호출하지 않는데요. 그래서 별도로 래핑 된 API 함수도 함께 적용할 수 있도록 useRequest를 조금 고쳤습니다.
import useSWR from 'swr'; import API, { http } from '~/api'; export default function useRequest(request, { initialData, ...config } = {}) { const APIWrapper = typeof request?.api === 'string' && request.api.split('.').reduce((a, c) => a[c], API); const usingAPIwrapper = typeof APIWrapper === 'function'; const usingAxios = typeof request?.url === 'string'; const key = usingAPIwrapper || usingAxios ? JSON.stringify(request) : request; const fetcher = () => usingAPIwrapper ? APIWrapper(request.params) : http(request || {}); const options = { ...config, initialData: initialData && { status: 200, statusText: 'InitialData', headers: {}, data: initialData, }, }; const { data: response, error, isValidating, mutate } = useSWR( request && [key, ...(request.deps || [])], fetcher, options, ); return { data: response?.data, response, error, isValidating, isLoading: !response?.data && !error, mutate, key, }; }
아래처럼 useRequest에 전달하는 객체에 api라는 이름으로 ‘articles.list’와 같이 API Path 문자열을 넘기면 API Wrapper에 만들어둔 함수를 API.articles.list() 와 같이 실행할 수 있습니다. 그리고 기존 useRequest 처럼 axios 옵션을 전달할 수도 있습니다.
const { data, error, isValidating, mutate } = useReqeust({ api: 'articles.list' });
그리고 Suspense와 ErrorBoundary를 조합하여 아래 샘플처럼 사용합니다.
import React, { Suspense } from 'react'; import { useParams } from 'react-router-dom'; import ErrorBoundary from '~/util/ErrorBoundary'; import useReqeust from '~/util/useReqeust'; const Detail = ({ id }) => { const { data } = useReqeust( { api: 'articles.detail', params: id, }, { suspense: true }, ); return ( <table border="1"> <tbody> <tr> <th>제목</th> <td>{data.subject}</td> </tr> <tr> <th>작성자</th> <td>{data.author}</td> </tr> <tr> <th>내용</th> <td>{data.content}</td> </tr> </tbody> </table> ); }; const ArticleDetail = () => { const { id } = useParams(); return ( <div style={{ padding: 30 }}> <h2>ArticleDetail</h2> <ErrorBoundary fallback={<div>내용을 불러올 수 없습니다.</div>}> <Suspense fallback={<div>Loading...</div>}> <Detail id={id} /> </Suspense> </ErrorBoundary> </div> ); }; export default ArticleDetail;
여기서 단순히 로딩 중임을 표시할 수도 있고, SWR이 캐시 데이터를 갱신할 때 업데이트 중임을 알리는 인디케이터를 붙일 수도 있습니다.
단순히 소개하고 기록해두려는 포스팅이라 때문에 내용이 상당히 부실한데요.. 어쨌든 SWR은 개발자경험을 살리면서 사용자경험도 동시에 향상할 수 있는 좋은 라이브러리인 것 같습니다.