[React] 데이터 패칭 라이브러리, SWR (Feat. 무한스크롤)

PinkTopaz·2023년 3월 12일
0
post-thumbnail

pic.me 에서는 데이터 패칭 라이브러리인 SWR을 도입했다.
그 전까지는 axios로 데이터를 get한 경험 밖에 없었기에 SWR을 쓰자는 리드개발자의 말을 듣고 SWR을 쓰는 이유가 무엇인지 공부했다.
실제로 사용을 해보면서 더욱 SWR의 장점이 명확하게 느껴졌는데, 이에 대한 내용을 이번 글에서 다뤄보려한다.


SWR이 무엇인가?

SWR 공식문서 에 따르면
SWR은 Next.js로 유명한 Vercel 팀에서 발표한 데이터 패칭 라이브러리로, 먼저 캐시(스태일)로부터 데이터를 반환한 후, fetch 요청(재검증)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략이다.

해당 설명을 조금 더 풀어서 이해해보자.
SWR은 추가적인 요청이 들어오면 최신 데이터가 아니더라도 캐싱된 데이터를 먼저 사용자에게 보여준다.
그리고 그 사이에 서버를 한 번 찔러서 재검증을 한다.
만약 받아온 데이터가 캐싱된 데이터와 다를 경우, 리렌더링을 통해 새로운 데이터를 사용자에게 보여준다.
간단히 말해, 재검증을 하는 동안 사용자에게 캐시 데이터를 먼저 보여주는 방식이라고 생각하면 된다.


SWR 사용법

다음은 pic.me 에서 결과페이지를 위한 데이터를 받아오기 위해 정의한 hook이다.

import useSWR from 'swr';

const fetcher = 
      (url: string) => client.get(url).then((res) => res.data);

const useGetVoteResult = (voteId: string) => {
  const { data, error } = useSWR<AxiosResponse<MakerVoteInfo>>(`/vote/library/${voteId}`, picmeGetFetcher, {
    errorRetryCount: 3,
  });

  return {
    voteResult: data?.data,
    isLoading: !error && !data?.data,
    isError: error,
  };
};

useSWR의 파라미터는 다음과 같다.

  • key : API Url이 들어간다.
  • fetcher : SWR의 key를 받고 데이터를 반환하는 비동기 함수
  • 다양한 옵션 (선택)

이렇게 useSWR은 fetch 함수 내에서 에러가 발생했다면 error를 반환하고, 그렇지 않을 경우 data를 반환한다.

상단 Hook에서는 옵션으로 errorRetryCount를 사용해주었는데, 해당 옵션을 사용하면 최초 시도 시에 에러가 발생하더라도 지정된 횟수만큼 데이터 불러오기를 다시 시도한다.
SWR 공식 문서에서 다양한 옵션을 설명하고 있으니, 이를 참고하여 때에 따라 필요한 옵션을 인자로 넣어주면 될 것 같다.


SWR의 장점

1. 캐시를 이용해 중복 요청을 제거해준다.

SWR은 데이터를 get하는 요청이 들어왔을 때, 바로 API를 호출하는 것이 아니라 기존에 캐싱된 데이터가 최신 데이터인지 확인하고 맞다면 해당 데이터를 반환한다.
하지만 기존에 캐싱된 데이터가 최신 데이터가 아니라면, 캐시 사용을 멈추고 그냥 서버에 요청을 보낸다.

아래처럼 useSWR을 호출하는 여러개의 컴포넌트가 있고, SWR로 데이터를 가져오는 useInfo가 존재한다고 가정하자.

// App.tsx
function App() {
  return <>
    <Component />
    <Component />
    <Component />
    <Component />
  </>;
}
// Component.tsx
function Component() {
  const { data, error } = useInfo()

  if (error) return <Error />
  if (!data) return <Loading />

  return <img src={data.imgUrl} />
}

코드만 보면 useInfo가 4번 호출될 것 같지만, SWR을 사용했기 때문에 서버에 계속해서 요청을 보내지 않고 캐싱된 데이터를 사용하면서 사실은 네트워크 요청이 최초 한 번만 이루어진다.

해당 부분은 API 중복 요청을 줄이면서 데이터 반환을 빠르게 하기 때문에 조금 더 reactive한 환경을 만들 수 있다는 점에서 SWR의 가장 큰 장점이라고 할 수 있다.

2. 자동 갱신

useSWR의 세번째 인자로 들어가는 옵션들 중 default가 true로 설정되어 있는 여러 갱신 조건들이 있다.

  • revalidateOnFocus : 다른 창을 포커스했다가 기존 창으로 돌아가면 기존 창의 데이터가 최신 데이터로 자동으로 갱신된다.
  • revalidateOnReconnect : 네트워크가 끊어졌다가 다시 회복이 될 때 데이터를 자동으로 갱신한다.

이 외에도 refreshInterval을 사용해서 데이터를 갱신하는 주기를 설정해줄 수도 있다.

useSWR('/api/todos', fetcher, { refreshInterval: 1000 })

useSWRInfinite로 무한스크롤 구현하기

SWR은 useSWR 외에도 페이지네이션과 인피니트 로딩을 위한 전용 API인 useSWRInfinite를 제공한다.

pic.me에서는 마감된 투표를 모아두는 라이브러리에서 가로, 세로 무한스크롤을 구현하면서 useSWRInfinite를 사용했다.
가로 무한스크롤을 구현한 코드를 통해 useSWRInfinite를 톺아보자.

export const useGetMonthlyLibraryInfo = (date: number) => {
  const { data, isLoading, error, size, setSize, mutate } = useSWRInfinite<AxiosResponse<VoteInfo[]>>(
    (idx: number, monthlyVoteInfo: AxiosResponse<VoteInfo[]>) => {
        //✅ 첫 요청 시에 요청하는 Key
      if (!idx) return `/vote/library/scroll/month?flag=0&date=${date}`;
    	//✅ 첫 요청 이후 요청하는 Key
      if (monthlyVoteInfo.data[0])
        return `/vote/library/scroll/month?flag=${
   monthlyVoteInfo.data[monthlyVoteInfo.data.length - 1].id
        }&date=${date}`;
      return null;
    },
    picmeGetFetcher,
    {
      errorRetryCount: 3,
    },
  );
  
 //...
};

useSWR과 다르게 추가된 부분 중 size와 setSize가 있는데 이 두 속성이 무한스크롤 구현을 편리하게 도와준다.

//가로 무한스크롤 컴포넌트 (MonthVoting.tsx)
const getMoreItem = useCallback(async () => {
    if (monthlyVoteInfoList) {
      setSize((prev) => prev + 1);
    } else {
      return;
    }
  }, []);

size는 가져올 페이지 및 반환될 페이지의 수이며, setSize는 가져와야 하는 페이지의 수를 설정해준다.
첫 요청을 통해 5개의 아이템을 받아오면 그 페이지가 0번 페이지가 되고, 그 다음 요청에서는 1번 페이지를 받아와야하기 때문에 size는 1이 된다.
그 결과 화면에는 0번과 1번 페이지가 모두 보일 수 있도록 하는 것이다.

mutate

만약 포커스가 이동했다가 다시 돌아오지 않았더라도, 설정한 refreshInterval 시간이 지나지 않았더라도 데이터 갱신을 요청하고 싶을 수 있다.
이와 같은 경우에 useSWR에서 반환하는 mutate를 쓰면 좋다.

//가로 무한스크롤 컴포넌트 (MonthVoting.tsx)
const handleDeleteVote = async (id: string) => {
    await deleteVote(id);
    return mutate();
  };

나 같은 경우에는 무한스크롤 아이템을 하나 삭제한 경우 바로 삭제된 데이터가 UI에 반영될 수 있도록 해야했기 때문에 handleDeleteVote에서 mutate를 리턴해 데이터 갱신을 일으켰다.


마치며

프로젝트를 진행하면서 axios보다 데이터를 가져오는 속도, 그리고 갱신에 대한 확실한 이점이 있는 SWR의 매력을 알게 되었다.
특히 데이터 갱신이 지속적으로 일어나야하는 페이지에서는 swr을 쓰지 않을 이유가 없다고 개인적으로 생각한다.
React-query도 SWR와 비슷한 기능을 한다고 알고 있는데, 아직 React-query는 사용해보지 않아서 해당 라이브러리도 사용해본 후 SWR과 React-query를 비교해보고 싶기도 하다.


참고 자료

https://swr.vercel.app/ko
https://iborymagic.tistory.com/135
https://satisfactoryplace.tistory.com/354

profile
🌱Connecting the dots🌱

4개의 댓글

깔끔한 포스팅 잘 읽었습니다 :) 저희 팀은 리액트 쿼리를 이용했는데, 캐시 데이터를 사용해서 불필요한 서버 요청을 막는다는 점에서 SWR 과 비슷한 것 같아요! 리액트 쿼리의 경우 키값이 같은 캐시 데이터에 한해, 자동으로 update 해 주는 기능을 제공해주고, 리렌더링이 일어나는 조건을 추가해줄 수 있는데 SWR도 그런 기능이 있는지 궁금하네요!!

답글 달기
comment-user-thumbnail
2023년 3월 19일

헉!! SWR로 무한스크롤까지 만들 수 있군요!! 저는 라이브러리 밖에 사용한 적이 없는데 한 번 해봐야겠어요! 근데 한 가지 궁금한 건 그럼 슬라이더에 내용이 다 다르면 컴포넌트를 map 없이 무한 스크롤로 만들 수 있는 건가요?? sizw, setSize 요 친구들이 그 역할을 하는 건가욥?

답글 달기
comment-user-thumbnail
2023년 3월 19일

아니 포스팅이 너무 깔꼼한데요?!! pic.me 서비스를 예시로 구체적으로 작성해주셔서 너무 감사해요! 체고~

답글 달기
comment-user-thumbnail
2023년 3월 19일

헉 진짜 미쳤어요 혹시 천재 아니신지..? SWR 항상 말로만 들었는데 이렇게 자세히 작성해주셔서 정말 감사합니다. PinkTopaz님 글 따라서 SWR 사용에 도전해보려구요!

답글 달기