요새 SWR에 대해서 배우고 있다. 이전에 SWR, react-query 등을 적용하고, 실제로 사용해보긴 했지만 사실 옵션이 어떻게 되어 있고 반환하는 값에 무엇이 있고, 정확히 어떻게 작동하는가에 대해서는 잘 모르고 있었다.
오늘은 그런 고급 사용법에 대해 알아보자.
SWR은 stale-while-revalidate
의 약자다. 무슨 뜻인가 풀어보자면 stale(cache) while revalidate, 즉 서버로의 요청을 하는 동안에 캐시를 나타낸다.. 뭐 그런 뜻을 의도한 것 같다.
SWR에 대해 본격적으로 알아보기 전에 알아두면 좋은 개념이 하나 있는데, 그것이 바로 낙관적인 UI이다.
어떤 것이냐하면, 유저가 서버에서 온 데이터를 로컬에서 수정했다고 치자. 그럼 어떤 일이 일어날까?
이런 흐름일 것이다. 그런데 데이터가 너무 방대한 나머지 요청이 끝나기까지 2초나 3초 정도가 걸린다면 어떨까? 유저는 화면에 업데이트된 데이터가 UI에 반영되는 것을 확인할 때 까지 기다려야 한다.
이런 현상을 해결하기위해 낙관적인 UI라는 개념이 나왔다. 낙관적인 UI를 적용하면 위의 과정이 다음과 같이 바뀐다.
즉 요청이 아무리 오래 걸려도 유저의 눈에는 업데이트한 데이터가 즉시 반영된 것 처럼 보이는 것이다.
이것은 아래에서 알아볼 SWR의 mutate에서 나오는 개념이다. 이제 SWR에 대해 알아보자.
SWR의 역할은 크게 두 가지이다.
이를 통해 로컬에서만 사용하는 전역 상태와 서버 상태를 분리할 수 있다. 이전에는 redux
와 redux-saga
등을 이용해서 서버 상태를 관리했는데, 그게 좀 복잡하기도 했고 반복되는 코드가 많았다. 그런 것들을 좀 더 나은 방법으로 해결해주는 역할을 한다고 평가할 수 있겠다.
기본적으로 useSWR
이라는 hook을 사용해서 데이터를 요청한다.
useSWR
의 문법은 다음과 같다.
const { data, error, isValidating, mutate } = useSWR(key, fetcher, option)
key
인자는 해당 useSWR
을 식별하기위한 문자열(혹은 함수, 배열, null)이다. 같은 key
를 사용하는 useSWR
들은 모두 같은 요청을 보내는 것으로 간주하고 최적화를 적용한다.
fetcher
인자는 매개변수로 key
를 받는 함수이다. 이 함수의 반환값이 useSWR
의 반환값 중 data
에 해당한다.
option
인자는 해당 useSWR
의 옵션을 담은 객체다. 옵션은 공식 문서에서 확인할 수 있다.
말로만 설명하면 좀 복잡할 수 있다. 아래는 실제 사용할 때의 모습이다.
// card.tsx
const fetcher = async url => {
const response = await axios.get(`someServer${url}`);
return response.data;
}
const { data, error } = useSWR('/cards', fetcher);
return (
<Fragment>
{
data.map(card => <p key={card.id}>{card.title}</p>)
}
</Fragment>
);
이런 느낌으로. 대충 어떻게 사용하는지 감을 잡을 수 있을 것이다.
옵션을 따로 설정하지 않았다면 여러가지 편리한 기능들이 기본값으로 적용된다. 윈도우 창이 포커싱 되었을 때 재요청 해주기도 하고, 브라우저의 네트워크가 끊겼다가 다시 연결되었을 때 재요청 해주기도 하고, 같은 key
를 사용하는 useSWR
의 호출이 2초 이내에 여러번 일어났을 경우 1번만 요청하기 등등... 이것저것 편리하다.
조금만 더 자세히 useSWR
에 대해 알아보자. useSWR
이 호출되고 fetcher
가 실행된 뒤, fetcher
가 반환된 값은 해당 key
를 사용하는 useSWR
의 cache가 된다.
무슨 말이냐면 fetcher
가 반환한 data
가 로컬 어딘가에 저장된다는 것이다. 그리고 해당 key
를 사용하는 useSWR
에 별도의 revalidate 요청이 없는 한, 해당 key
를 사용하는 useSWR
의 data
는 caching된 상태를 유지하는 것이다.
이것이 SWR
라이브러리의 핵심 개념이다. 데이터를 요청하고, 캐싱해서 재사용한다. 그리고 새로운 revalidate 요청이 들어오면 서버로 데이터를 요청하고, 다시 캐싱하고 재사용한다. 이것의 반복이다.
이상의 개념만 알고 있으면 SWR
의 80%는 알았다고 해도 과언이 아니다.
revalidate라는 단어가 의미하는 것은, 특정 useSWR
의 fetcher
를 호출해서 서버에서 반환된 data
로 해당 useSWR
의 cache를 갱신한다는 것이다. 이것도 중요한 개념이다.
useSWR
의 반환값 중 mutate
라는 함수가 있다. mutate
는 언제 데이터를 최신화 할 것인지를 컨트롤 할 수 있게 해주는 역할이라고 생각한다.
mutate
의 사용방법은 좀 여러가지이고, 헷갈리기 쉬우니 잘 구분해야한다.
일단 mutate
의 문법을 살펴보자.
const data = mutate(key, data, option);
key
인자는 최신화하고자 하는 useSWR
의 key
이다. 내가 /cards
라는 key
를 사용하는 useSWR
을 최신화하려 한다면 /cards
를 key
인자로 주면 된다.
data
인자는 해당 useSWR
을 최신화하기 위한 data다. 정확히는 해당 useSWR
의 cache를 data
로 바꾸는 것이다.
option
인자는 해당 mutate
의 옵션 객체다. 이 또한 공식 문서에서 확인할 수 있다.
반환값인 data
는 2번째 인자인 data
와 동일하다.
나는 mutate
함수에서 굉장히 헷갈렸다. 하지만 위에서 설명한 useSWR
과 cache 개념만 잘 이해했다면, 덜 헷갈릴 것이라고 단언할 수 있다.
mutate
는 정확히는 해당 key
를 사용하는 useSWR
의 cache를 data
로 최신화 시켜준다.
그리고 option
객체를 따로 넘겨주지 않았다면 해당 key
를 사용하는 useSWR
를 revalidate한다. <<<<< 이게 정말 중요하다!!!!
내가 mutate
로 cache를 최신화 했다고 해도, 서버의 데이터를 최신화 시키지 않았다면 해당 useSWR
이 revalidate 되면서 서버에 있는 최신화 이전의 data
가 cache로 갱신된다.
그리고 이런... mutate
이후의 revalidate를 mutate
의 option
객체로 어느정도 컨트롤 할 수 있다.
option
객체에 그냥 falsy한 값을 주면 mutate
이후에 해당 useSWR
을 revalidate 하지 않는다. 그래서 기본값은 true인 것 같다.
객체를 통해 좀 더 세세한 옵션을 지정할 수 있긴 한데, 그랬더니 내가 생각한대로 잘 동작하질 않아서... 그에 대해선 좀 더 알아봐야 한다.
mutate
는 두 가지 경로로 접근할 수 있다.
useSWRConfig
의 반환값 mutate
useSWRConfig
이라는 hook의 반환값으로써의 mutate
는 반드시 key
인자를 넘겨주어야 한다.useSWR
의 반환값 mutate
useSWR
의 반환값으로써의 mutate
는 해당 useSWR
의 key
가 이미 바인딩되어 있는 상태이다. 그래서 굳이 key
인자를 넘겨주지 않아도 된다.mutate
의 data
인자는 비워놓을 수 있다. 만일 data
인자를 넘겨주지 않은 채 mutate
를 호출한다면 해당 useSWR
을 revalidate 하는 행동만을 한다.
이 글만 보고 SWR을 손바닥 뒤집듯이 다루기는 쉽지 않을 것이다. 이 글을 보고, 직접 API 요청을 해보고, 캐시를 다루고, 개발자 도구 - 네트워크에서 어떻게 요청이 가는지 관찰하고... 하다 보면 어느새 SWR을 내 생각대로 다룰 수 있게 될 것이다.
다음 포스트에선 useSWRInfinite에 대해서 알아볼 계획이다. 이걸 다 배우고 나면 SWR으로 멋지게 서버 상태를 전역으로 관리하자.