SWR 심층탐구 - useSWRInfinite

불꽃남자·2022년 8월 15일
1

SWR 심층탐구

목록 보기
2/3
post-custom-banner

개요

저번 포스팅에선 useSWR과 mutate에 대해 알아보았다.

사실 useSWR이 기본형태이고, 좀 더 다양한 상황에 맞게 쓸 수 있게 파생된 useSWR hook들이 몇 가지 있다.

오늘은 그 중 useSWRInfinite에 대해 알아보자.

useSWRInfinite

위에서 이야기 한 것처럼 특수한 상황에서 사용하는 useSWR이 몇 가지 있는데, 개중에서 가장 빈번하게 쓰이는 것이 useSWRInfinite 이다. 주로 pagination을 적용해야하는 상황에서 사용된다.

공식 문서

우선 useSWRInfinite의 문법을 알아보자.

const { data, error, isValidating, mutate, size, setSize } = useSWRInfinite(
  getKey, fetcher?, options?
)

getKey 인자는 pageIndex, previousPageData를 인자로 받고, key를 반환하는 함수다. 만일 getKey 함수가 null 을 반환한다면 페이지의 요청이 일어나지 않는다.
여기서 pageIndex는 말 그대로 현재 page의 Index, previousPageData는 이전 페이지에 담긴 data이다.

fetcheroptionsuseSWR과 같다. 다만 options 객체에는 useSWRInfinite에만 있는 prop이 몇 개 있다.

반환값 또한 useSWR과 같다. 다만 size, setSize라는 반환값이 추가로 있다.

size 반환값은 가져올 페이지 및 반환될 페이지의 수 이다. 쉽게 말하자면 현재 반환된 data가 몇 페이지까지의 데이터인지를 나타내는 수이다. size가 1이라면 현재 data는 1페이지까지의 데이터인 것이고, 2라면 현재 data는 2페이지 까지의 데이터라는 것이다.

setSize 반환값은 함수이다. setSize 함수는 인자로 pageIndex를 받는다. setSize(3)을 호출하면, 해당 useSWRInfinitegetKey 함수가 호출되고, getKey 함수의 pageIndex 매개변수가 3이 된다. 그럼 새로운 pageIndex로 revalidate 되고 data가 갱신된다.
쉽게 말하자면 setSize(3)을 호출하면 해당 useSWRInfinitedata가 3페이지까지의 데이터로 갱신된다는 것이다.

한 번에 이해했다면 똑똑한 것이다. 잘 이해가 안 됐다면 정상이다.

좀 더 상세히

useSWRInfinite의 동작을 좀 더 상세히 설명하자면...

  1. getKey 함수가 실행된다. getKey 함수가 최초 호출됐을 때의 pageIndex 매개변수는 0. API 0번째 데이터를 요청하는 URL을 key로써 반환한다.(이 부분은 개발자가 해야함)
  2. fetcher 함수가 실행된다. fetcher 함수의 key 매개변수는 getKey 함수의 반환값. 0번째 페이지 데이터를 요청했고, 응답 데이터를 반환했다고 하자. 반환값은 data 배열에 담김

이제 setSize(size => size + 1) 함수가 호출됐다고 치자.

  1. getKey 함수가 실행된다. pageIndex 매개변수는 0.
  2. fetcher 함수가 실행된다. 역시 0번째 페이지 데이터를 요청하고 반환값이 data 배열에 push됨
  3. getKey 함수가 다시 실행된다. 이번엔 pageIndex 매개변수가 1이다.
  4. fetcher 함수가 실행된다. 1번째 페이지 데이터를 요청하고 반환값이 data 배열에 push됨

그럼 아마 data 배열은 다음과 같을 것이다.

[
  [ // 0번째 페이지에 담긴 user 정보. "/users?page=0" API의 반환값
    { name: 'Alice', ... },
    { name: 'Bob', ... },
    { name: 'Cathy', ... },
    ...
  ],
  [ // 1번째 페이지에 담긴 user 정보. "/users?page=1" API의 반환값
    { name: 'John', ... },
    { name: 'Paul', ... },
    { name: 'George', ... },
    ...
  ],
  ...
]

이제 느낌이 올 것이다. setSize(4)를 호출하면 getKey 함수가 pageIndex 매개변수로 0 을 받고 key를 반환, fetcher 함수가 실행, 반환값이 data 배열에 push, getKey 함수가 pageIndex 매개변수로 1 을 받고 fetcher함수가 실행... 2를 받고... 3을 받고...
fetcher 함수가 실행될 때 마다 반환값이 차례차례 data 배열에 push된다는 것이다.

그 후 우리는 /users?page=0 부터 /users?page=4 까지 의 응답값이 담긴 data 배열을 얻게 될 것이다


이번엔 공식문서의 실제 사용 예시 코드를 관찰해보자.

/ 각 페이지의 SWR 키를 얻기 위한 함수,
// `fetcher`에 의해 허용된 값을 반환합니다.
// `null`이 반환된다면, 페이지의 요청은 시작되지 않습니다.
const getKey = (pageIndex, previousPageData) => {
  if (previousPageData && !previousPageData.length) return null // 끝에 도달
  return `/users?page=${pageIndex}&limit=10`                    // SWR 키
}

function App () {
  const { data, size, setSize } = useSWRInfinite(getKey, fetcher)
  if (!data) return 'loading'

  // 이제 모든 users의 수를 계산할 수 있습니다
  let totalUsers = 0
  for (let i = 0; i < data.length; i++) {
    totalUsers += data[i].length
  }

  return <div>
    <p>{totalUsers} users listed</p>
    {data.map((users, index) => {
      // `data`는 각 페이지의 API 응답 배열입니다.
      return users.map(user => <div key={user.id}>{user.name}</div>)
    })}
    <button onClick={() => setSize(size + 1)}>Load More</button>
  </div>
}

곰곰히 읽어보면 이해가 될 것이다.

이게 useSWRInfinite의 기본적인 것의 전부다. 나머지는 본인이 응용해서 어떻게 잘 써야한다.

useSWRInfinite의 key

useSWRInfinitekey는 일반 useSWRkey와는 그 생김새가 좀 다르다.
정확히는 우리가 getKey에서 반환한 문자열 앞에 $inf$ 라는 접두가 붙는다.

useSWRInfinite를 위한 key를 만들기 위해선 unstable_serialize라는 함수를 swr/infinite로부터 import 해서 사용해야한다.
그런데 이름에서부터 알 수 있듯이 현재(1.3.0버전)로썬 이 함수가 불안정한 모양이다. 뭔가 생각한대로 동작하질 않는다. 그래서 그냥 useSWRInfinite가 반환한 바인딩된 mutate를 사용하는 것을 추천한다.

🥀

다음 포스트는 useSWRImmutable에 대해 알아볼 생각이다. 이건 내용이 좀 짧다.
pagination 형식의 UI는 단순히 게시판을 떠올릴 수도 있지만, 웹 페이지의 스크롤이 끝에 닿을 때 쯤 setSize 함수의 인자로 size + 1을 넘기고 호출함으로써 무한 스크롤 UI을 쉽게 만들 수도 있다. 주로 그 두 가지가 사용되는 모양이다.

SWR을 공부하다보면 머리를 잘 굴려서 더 재밌는 UI도 만들 수 있겠다는 자신감마저 생긴다.

profile
프론트엔드 꿈나무, 탐구자.
post-custom-banner

0개의 댓글