구버전/React

[SWR] Data의 reavalidate, mutate 활용

고래강이 2023. 11. 3. 11:39

swr의 기본적인 사용방법에 대해서는 다른 글을 참고하는게 낫다

 

이것은 오로지 swr을 통해서 가져온 data를 어떻게 revalidate하는게 좋고 mutate를 어떨 때 사용하는게 좋고 optimistic UI를 어떻게 사용해야하는지에 대해서 내가 볼려고 적어 놓은 것임


개요

  • SWR의 이점
  • revalidate란?
  • mutate란?
  • optimisticUI란?
  • key의 활용 (조건부 데이터 가져오기, token전달)

 


SWR 장점

  • 빠르고, 가볍고, 재사용 가능한 데이터 가져오기
    • React-query와 마찬가지로 데이터를 불러올 때 사용되는데 SWRconfig를 통해 전역설정만 하고 fetcher만 설정해 놓는다면 간단하게 사용할 수 있는 구조이다. (전역설정 안해도 됨 그러면 따로 fetcher를 매번 작성해야 함)
    • key를 여러방식으로 넣을 수 있어서 이를 통해서 다양하게 활용할 수 있다.
  • 내장된 캐시 및 요청 중복 제거
    • 현재 보이는 코드구조에서 useUser()라는 swr을 통해서 데이터를 불러오는 method가 있다 이는 2개의 컴포넌트에서 각각 호출이 되는데 캐시된 데이터를 가지고있기 떄문에 실질적으로는 요청1번과 캐시된 데이터를 사용한 것 1번인 것이다.
    • 이는 이전처럼 root에서 props로 내리는 전통적인 방식에 비해서 독립적이여서 성능에서도 좋으며 코드유지에 도움을 많이 줍니다. 이때 같은 swr key를 사용해야하는 것을 꼭 기억해야 한다. 
    • 그러면 데이터 최신화가 안되고 계속 캐시된 데이터를 끌어오는 것 아닐까라고 생각하기 쉽지만 그렇기 위해 여러 옵션이 있으니 그걸 알아보도록 하자.
// 페이지 컴포넌트
function useUser (id) {
  const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
 
  return {
    user: data,
    isLoading,
    isError: error
  }
}

function Page () {
  return <div>
    <Navbar />
    <Content />
  </div>
}
 
// 자식 컴포넌트
 
function Navbar () {
  return <div>
    ...
    <Avatar />
  </div>
}
 
function Content () {
  const { user, isLoading } = useUser()
  if (isLoading) return <Spinner />
  return <h1>Welcome back, {user.name}</h1>
}
 
function Avatar () {
  const { user, isLoading } = useUser()
  if (isLoading) return <Spinner />
  return <img src={user.avatar} alt={user.name} />
}
  • SSR / ISR / SSG 지원
    • option에서 여러 방식을 통해서 구현 가능

 


Revalidate

swr은 기본적으로 revalidateOnFocus   기능이 활성화 되어있기때문에 사용자가 foucs하거나 탭을 전환할 시 자동으로 데이터를 갱신한다.

 refreshInterval   를 통해서 일정 시간 간격으로 데이터를 갱신할 수도 있음

기타 등등 여러 옵션을 통해서 데이터를 갱신할 수 있음!

대표적인 사용경우

  • 포커스 시 갱신
  • 인터벌(시간간격) 시 갱신
  • 재연결 시 갱신
  • 마운트 시 갱신

하지만 revalidate의 경우 해당 키값에 해당하는 캐시된 데이터 전부를 무효화하고 새로 받아오기 때문에 조금 효율성이 떨어질 수 있다. 그래서 우리는 상황에 따라 mutate를 써서 캐시된 데이터의 일부만 업데이트하고 업데이트 된 캐시데이터와 실제 DB의 데이터를 일치하는 방법을 활용해야 한다.


Mutate

mutate를 통해서 클라이언트에 캐시되어있는 캐시데이터를 업데이트 할 순 있지만 원격데이터를 업데이트 할 수는 없다. 그러니 updateUser라는 post, put, delete 요청을 통해서 원격데이터를 업데이트 할 api를 따로 할당을 해주어야 한다.

mutate의 api상 요청을 저렇게 집어넣을 수도 있어서 코드가 깔끔해지겠구나

try {
  const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
  // 여기에서 사용자를 업데이트 하는 동안 발생한 오류를 처리하세요.
}

useSWRMutation을 이용해서 이러한 방법으로 사용할 수도 있다.

import useSWRMutation from 'swr/mutation'
 
async function sendRequest(url, { arg }: { arg: { username: string } }) {
  return fetch(url, {
    method: 'POST',
    body: JSON.stringify(arg)
  }).then(res => res.json())
}
 
function App() {
  const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest, /* options */)
 
  return (
    <button
      disabled={isMutating}
      onClick={async () => {
        try {
          const result = await trigger({ username: 'johndoe' }, /* options */)
        } catch (e) {
          // 에러 핸들링
        }
      }}
    >
      Create User
    </button>
  )
}

useSWRMutation은 useSWR과 캐시 저장소를 공유하기 때문에 이로 인해서 발생하는 문제점도 있을 수 있을 것 같다

하지만 예상과는 달리 두개를 동시에 사용했을 경우 경합 상태를 알아서 피하도록 설계가 되어있는데 아래와 같이 코드를 구성을 했을 때 get요청이 더 일찍 시작되고 늦게 끝나는 경우에는 데이터의 경합이 일어날 수 있다.

이러한 경우 useSWRMutation이 자동으로 처리를 해서 useSWR에 진행중인 요청을 버리고 재검증을 하여 오래된 데이터가 표시되지 않도록 한다.

function Profile() {
  const { data } = useSWR('/api/user', getUser, { revalidateInterval: 3000 })
  const { trigger } = useSWRMutation('/api/user', updateUser)
 
  return <>
    {data ? data.username : null}
    <button onClick={() => trigger()}>Update User</button>
  </>
}

조건부 가져오기

간단하게 삼항연산자를 key값에 넣어서 활성화를 시키는 방법임

// 조건부 가져오기
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)
 
// ...또는 falsy 값 반환
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fetcher)
 
// ...또는 user.id가 정의되지 않았을 때 에러 throw
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher)

key값에 falsey한 값이 들어가면 에러를 내는 것이지 실행이 안되는 것인지 테스트를 해보자

function MyProjects () {
  const { data: user } = useSWR('/api/user')
  const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id)
  // 함수를 전달할 때, SWR은 반환 값을 `key`로 사용합니다.
  // 함수가 falsy를 던지거나 반환한다면,
  // SWR은 일부 의존성이 준비되지 않은 것을 알게 됩니다.
  // 이 예시의 경우 `user.id`는 `user`가 로드되지 않았을 때
  // 에러를 던집니다.
 
  if (!projects) return 'loading...'
  return 'You have ' + projects.length + ' projects'
}

 

Token 전달

이런식으로 token을 전달 할 수도 있음

근데 사실 axios를 사용하면 interceptor에서 token에 대한 처리를 해주는 것이 가장 나을 것 같고 간단하게 swr만 이용해서 데이터를 관리하는 경우라면 이렇게 사용해볼만한 것 같다.

const { data: user } = useSWR(['/api/user', token], ([url, token]) => fetchWithToken(url, token))