저번 포스팅에선 useSWR과 mutate에 대해 알아보았다.
사실 useSWR이 기본형태이고, 좀 더 다양한 상황에 맞게 쓸 수 있게 파생된 useSWR hook들이 몇 가지 있다.
오늘은 그 중 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
이다.
fetcher
와 options
는 useSWR
과 같다. 다만 options
객체에는 useSWRInfinite
에만 있는 prop이 몇 개 있다.
반환값 또한 useSWR
과 같다. 다만 size
, setSize
라는 반환값이 추가로 있다.
size
반환값은 가져올 페이지 및 반환될 페이지의 수 이다. 쉽게 말하자면 현재 반환된 data
가 몇 페이지까지의 데이터인지를 나타내는 수이다. size
가 1이라면 현재 data
는 1페이지까지의 데이터인 것이고, 2라면 현재 data
는 2페이지 까지의 데이터라는 것이다.
setSize
반환값은 함수이다. setSize
함수는 인자로 pageIndex
를 받는다. setSize(3)
을 호출하면, 해당 useSWRInfinite
의 getKey
함수가 호출되고, getKey
함수의 pageIndex
매개변수가 3이 된다. 그럼 새로운 pageIndex
로 revalidate 되고 data가 갱신된다.
쉽게 말하자면 setSize(3)
을 호출하면 해당 useSWRInfinite
의 data
가 3페이지까지의 데이터로 갱신된다는 것이다.
한 번에 이해했다면 똑똑한 것이다. 잘 이해가 안 됐다면 정상이다.
useSWRInfinite
의 동작을 좀 더 상세히 설명하자면...
getKey
함수가 실행된다. getKey
함수가 최초 호출됐을 때의 pageIndex
매개변수는 0
. API 0번째 데이터를 요청하는 URL을 key
로써 반환한다.(이 부분은 개발자가 해야함)fetcher
함수가 실행된다. fetcher
함수의 key
매개변수는 getKey
함수의 반환값. 0번째 페이지 데이터를 요청했고, 응답 데이터를 반환했다고 하자. 반환값은 data
배열에 담김이제 setSize(size => size + 1)
함수가 호출됐다고 치자.
getKey
함수가 실행된다. pageIndex
매개변수는 0
.fetcher
함수가 실행된다. 역시 0번째 페이지 데이터를 요청하고 반환값이 data
배열에 push됨getKey
함수가 다시 실행된다. 이번엔 pageIndex
매개변수가 1이다.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
는 일반 useSWR
의 key
와는 그 생김새가 좀 다르다.
정확히는 우리가 getKey
에서 반환한 문자열 앞에 $inf$
라는 접두가 붙는다.
useSWRInfinite
를 위한 key
를 만들기 위해선 unstable_serialize
라는 함수를 swr/infinite
로부터 import 해서 사용해야한다.
그런데 이름에서부터 알 수 있듯이 현재(1.3.0버전)로썬 이 함수가 불안정한 모양이다. 뭔가 생각한대로 동작하질 않는다. 그래서 그냥 useSWRInfinite
가 반환한 바인딩된 mutate
를 사용하는 것을 추천한다.
다음 포스트는 useSWRImmutable
에 대해 알아볼 생각이다. 이건 내용이 좀 짧다.
pagination 형식의 UI는 단순히 게시판을 떠올릴 수도 있지만, 웹 페이지의 스크롤이 끝에 닿을 때 쯤 setSize
함수의 인자로 size + 1
을 넘기고 호출함으로써 무한 스크롤 UI을 쉽게 만들 수도 있다. 주로 그 두 가지가 사용되는 모양이다.
SWR을 공부하다보면 머리를 잘 굴려서 더 재밌는 UI도 만들 수 있겠다는 자신감마저 생긴다.