SWR 심층탐구 - useSWR, mutate

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

SWR 심층탐구

목록 보기
1/3

개요

요새 SWR에 대해서 배우고 있다. 이전에 SWR, react-query 등을 적용하고, 실제로 사용해보긴 했지만 사실 옵션이 어떻게 되어 있고 반환하는 값에 무엇이 있고, 정확히 어떻게 작동하는가에 대해서는 잘 모르고 있었다.
오늘은 그런 고급 사용법에 대해 알아보자.

SWR이란

SWR은 stale-while-revalidate의 약자다. 무슨 뜻인가 풀어보자면 stale(cache) while revalidate, 즉 서버로의 요청을 하는 동안에 캐시를 나타낸다.. 뭐 그런 뜻을 의도한 것 같다.

낙관적인 UI

SWR에 대해 본격적으로 알아보기 전에 알아두면 좋은 개념이 하나 있는데, 그것이 바로 낙관적인 UI이다.
어떤 것이냐하면, 유저가 서버에서 온 데이터를 로컬에서 수정했다고 치자. 그럼 어떤 일이 일어날까?

  1. 서버에 업데이트 요청을 보냄
  2. 요청이 끝날 때 까지 기다림
  3. 요청이 완료되면 화면에 업데이트된 데이터가 반영된 UI를 나타냄

이런 흐름일 것이다. 그런데 데이터가 너무 방대한 나머지 요청이 끝나기까지 2초나 3초 정도가 걸린다면 어떨까? 유저는 화면에 업데이트된 데이터가 UI에 반영되는 것을 확인할 때 까지 기다려야 한다.

이런 현상을 해결하기위해 낙관적인 UI라는 개념이 나왔다. 낙관적인 UI를 적용하면 위의 과정이 다음과 같이 바뀐다.

  1. 로컬에서 데이터를 업데이트 시키고, 반영된 UI를 즉시 나타냄
  2. 서버에 업데이트 요청을 보냄
  3. 요청이 끝날 때 까지 기다림
  4. 요청이 완료되면 서버 데이터와 로컬 데이터가 일치됨

즉 요청이 아무리 오래 걸려도 유저의 눈에는 업데이트한 데이터가 즉시 반영된 것 처럼 보이는 것이다.

이것은 아래에서 알아볼 SWR의 mutate에서 나오는 개념이다. 이제 SWR에 대해 알아보자.

SWR의 역할

SWR의 역할은 크게 두 가지이다.

  1. 서버로의 데이터 요청 시점 컨트롤
    데이터를 언제 요청할 것인지 컨트롤 하기가 용이하고, 같은 API를 요청하는 컴포넌트가 여러 개 있으면 해당 API로의 요청이 단 한 번만 이루어진다.
  2. 반환된 데이터를 전역으로 관리하기 용이
    또한 같은 API로 요청한 컴포넌트들은 반환된 데이터를 서로 공유한다. 내부적으로 어딘가에 응답 데이터를 저장해놓고 공유하는 것으로 보인다.

이를 통해 로컬에서만 사용하는 전역 상태와 서버 상태를 분리할 수 있다. 이전에는 reduxredux-saga등을 이용해서 서버 상태를 관리했는데, 그게 좀 복잡하기도 했고 반복되는 코드가 많았다. 그런 것들을 좀 더 나은 방법으로 해결해주는 역할을 한다고 평가할 수 있겠다.

useSWR

기본적으로 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과 cache

조금만 더 자세히 useSWR에 대해 알아보자. useSWR이 호출되고 fetcher가 실행된 뒤, fetcher가 반환된 값은 해당 key를 사용하는 useSWR의 cache가 된다.

무슨 말이냐면 fetcher가 반환한 data가 로컬 어딘가에 저장된다는 것이다. 그리고 해당 key를 사용하는 useSWR에 별도의 revalidate 요청이 없는 한, 해당 key를 사용하는 useSWRdata는 caching된 상태를 유지하는 것이다.

이것이 SWR 라이브러리의 핵심 개념이다. 데이터를 요청하고, 캐싱해서 재사용한다. 그리고 새로운 revalidate 요청이 들어오면 서버로 데이터를 요청하고, 다시 캐싱하고 재사용한다. 이것의 반복이다.

이상의 개념만 알고 있으면 SWR의 80%는 알았다고 해도 과언이 아니다.

useSWR과 revalidate

revalidate라는 단어가 의미하는 것은, 특정 useSWRfetcher를 호출해서 서버에서 반환된 data로 해당 useSWR의 cache를 갱신한다는 것이다. 이것도 중요한 개념이다.

mutate

useSWR 의 반환값 중 mutate 라는 함수가 있다. mutate는 언제 데이터를 최신화 할 것인지를 컨트롤 할 수 있게 해주는 역할이라고 생각한다.

mutate의 사용방법은 좀 여러가지이고, 헷갈리기 쉬우니 잘 구분해야한다.
일단 mutate의 문법을 살펴보자.

const data = mutate(key, data, option);

key 인자는 최신화하고자 하는 useSWRkey이다. 내가 /cards라는 key를 사용하는 useSWR을 최신화하려 한다면 /cardskey 인자로 주면 된다.
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 option

그리고 이런... mutate 이후의 revalidate를 mutateoption 객체로 어느정도 컨트롤 할 수 있다.
option 객체에 그냥 falsy한 값을 주면 mutate 이후에 해당 useSWR을 revalidate 하지 않는다. 그래서 기본값은 true인 것 같다.
객체를 통해 좀 더 세세한 옵션을 지정할 수 있긴 한데, 그랬더니 내가 생각한대로 잘 동작하질 않아서... 그에 대해선 좀 더 알아봐야 한다.

mutate와 key 바인딩

mutate 는 두 가지 경로로 접근할 수 있다.

  1. useSWRConfig의 반환값 mutate
    useSWRConfig이라는 hook의 반환값으로써의 mutate는 반드시 key 인자를 넘겨주어야 한다.
  2. useSWR의 반환값 mutate
    어떤 useSWR의 반환값으로써의 mutate는 해당 useSWRkey가 이미 바인딩되어 있는 상태이다. 그래서 굳이 key 인자를 넘겨주지 않아도 된다.

mutate와 data 인자

mutatedata 인자는 비워놓을 수 있다. 만일 data 인자를 넘겨주지 않은 채 mutate를 호출한다면 해당 useSWR을 revalidate 하는 행동만을 한다.

🍀

이 글만 보고 SWR을 손바닥 뒤집듯이 다루기는 쉽지 않을 것이다. 이 글을 보고, 직접 API 요청을 해보고, 캐시를 다루고, 개발자 도구 - 네트워크에서 어떻게 요청이 가는지 관찰하고... 하다 보면 어느새 SWR을 내 생각대로 다룰 수 있게 될 것이다.

다음 포스트에선 useSWRInfinite에 대해서 알아볼 계획이다. 이걸 다 배우고 나면 SWR으로 멋지게 서버 상태를 전역으로 관리하자.

profile
프론트엔드 꿈나무, 탐구자.

0개의 댓글