[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - SWR

강경서·2023년 7월 23일
0
post-thumbnail

🛒 SWR


팀 프로젝트에서 전역 상태 관리를 SWR을 이용하기로 했습니다. 이전에 SWR을 잠깐 접해보았을 떄는 데이터를 가져오는 데에만 사용했습니다. 이번 프로젝트에서는 SWR을 통해 데이터를 가져오는 컴포넌트를 만들어 해당 데이터를 전역적으로 사용하기로 계획했습니다.


SWR이란?

"SWR"이라는 이름은 HTTP RFC 5861(opens in a new tab)에 의해 알려진 HTTP 캐시 무효 전략인 stale-while-revalidate에서 유래되었습니다. SWR은 먼저 캐시(stale)로부터 데이터를 반환한 후, fetch 요청(revalidate)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략입니다.

즉, 백그라운드에서 캐시를 재검증(revalidate)하는 동안에 기존에 캐시된 데이터를 사용하게 한다는 것입니다. 이는 에러를 반환하더라도 캐시된 데이터를 활용할 수 있게 함으로써 데이터를 계속 호출하는데 시간을 쓰지 않고, 캐시된 데이터를 이용해 효율적으로 동작합니다.


SWR 시작하기

JSON 데이터를 사용하는 일반적인 RESTful API라면 먼저 네이티브 fetch의 단순한 래퍼인 fetcher 함수를 생성해야 합니다.

const fetcher = (...args) => fetch(...args).then(res => res.json())

그 다음, useSWR을 임포트하고 함수 컴포넌트 내에서 사용하여 시작할 수 있습니다.

import useSWR from 'swr'
 
function Profile () {
  const { data, error, isLoading } = useSWR('/api/user/123', fetcher)
 
  if (error) return <div>failed to load</div>
  if (isLoading) return <div>loading...</div>
 
  // 데이터 렌더링
  return <div>hello {data.name}!</div>
}

일반적으로 요청에는 "ready", "error", "loading"의 세 가지 상태가 있습니다. data, error, isLoading 값을 사용하여 요청의 현재 상태를 확인하고 해당 UI를 반환합니다.


재사용 가능하게 만들기

웹 앱을 구축할 때, UI의 많은 곳에서 데이터를 재사용할 필요가 있습니다. SWR 이용하면 재사용 가능한 데이터 hook을 만드는 것이 간단합니다.

function useUser (id) {
  const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
 
  return {
    user: data,
    isLoading,
    isError: error
  }
}
function Avatar ({ id }) {
  const { user, isLoading, isError } = useUser(id)
 
  if (isLoading) return <Spinner />
  if (isError) return <Error />
  return <img src={user.avatar} />
}

위와 같이 데이터 컴포넌트를 만들어 사용하면 prop을 통한 데이터 전달이 아닌 컴포넌트를 불러와서 바로 데이터를 사용이 가능하여 데이터를 파악하기도 유지하기도 더 간단합니다.

또한 동일한 SWR 키를 사용하면 그 요청을 자동으로 중복 제거, 캐시, 공유되므로, 단 한 번의 요청만 API로 전송됩니다. 그리고 애플리케이션은 이제 사용자 포커스나 네트워크 재연결 시에 데이터를 갱신할 수 있습니다. 이는 사용자의 노트북이 슬립으로부터 깨어나거나 브라우저 탭을 전환할 때 자동으로 데이터가 갱신된다는 것을 의미합니다.


API

const { data, error, isLoading, isValidating, mutate } = useSWR(key, fetcher, options)

파라미터

  • key: 요청을 위한 고유한 키 문자열(또는 함수 / 배열 / null)
  • fetcher: (옵션) 데이터를 가져오기 위한 함수를 반환하는 Promise
  • options: (옵션) SWR hook을 위한 옵션 객체 자세히보기

반환 값

  • data: fetcher가 이행한 주어진 키에 대한 데이터(로드되지 않았다면 undefined)
  • error: fetcher가 던진 에러(또는 undefined)
  • isLoading: 진행 중인 요청이 있고 "로드된 데이터"가 없는 경우. 폴백 데이터와 이전 데이터는 "로드된 데이터"로 간주하지 않습니다.
  • isValidating: 요청이나 갱신 로딩의 여부
  • mutate(data?, options?): 캐시 된 데이터를 뮤테이트하기 위한 함수

전역 설정

SWRConfig 컨텍스트는 모든 SWR hook에 대한 전역 설정을 제공합니다.

<SWRConfig value={options}>
  <Component/>
</SWRConfig>

이 예시에서, 모든 SWR hook은 제공된 동일한 fetcher를 사용해 JSON 데이터를 로드하고 기본적으로 3초마다 갱신합니다.

import useSWR, { SWRConfig } from 'swr'
 
function Dashboard () {
  const { data: events } = useSWR('/api/events')
  const { data: projects } = useSWR('/api/projects')
  const { data: user } = useSWR('/api/user', { refreshInterval: 0 }) // 오버라이드
 
  // ...
}
 
function App () {
  return (
    <SWRConfig
      value={{
        refreshInterval: 3000,
        fetcher: (resource, init) => fetch(resource, init).then(res => res.json())
      }}
    >
      <Dashboard />
    </SWRConfig>
  )
}

SWRConfig은 중첩을 통해 Object나 Function을 받을 수 있습니다. 또한 useSWRConfig hook을 사용해 전역 설정과 mutate 및 cache를 얻을 수 있습니다. 자세히보기


자동 갱신

SWR은 데이터를 자동으로 갱신합니다. 뮤데티션을 사용하면 수동으로 데이터를 갱신할 수 있습니다.

  • 포커스 시에 갱신하기: 페이지에 다시 포커스하거나 탭을 전환할 때, SWR은 자동으로 데이터를 갱신합니다. 최신 상태로 즉시 동기화할 수 있어 유용합니다. 오래된 모바일 탭 또는 슬립 모드로 빠진 노트북과 같은 시나리오에서 데이터를 새로 고치는데 유용합니다.
  • 인터벌 시에 갱신하기: 많은 경우에 데이터는 다중 기기, 다중 사용자, 다중 탭으로 인해 변경됩니다. SWR은 시간이 지남에 따라 화면상의 데이터를 업데이트할 수 있습니다.
  • 재연결 시에 갱신하기:사용자가 온라인으로 돌아올 때 갱신하는 것 또한 유용합니다.데이터를 항상 최신으로 보장하기 위해 네트워크가 회복될 때 SWR은 자동으로 갱신합니다.

리소스가 불변할 경우, 다시 갱신해도 변경되지 않습니다. 이런 경우를 대비해 모든 종류의 자동 갱신을 비활성화 할 수 있습니다.


조건부 가져오기

조건부

null을 사용하거나 함수를 key로 전달하여 데이터를 조건부로 가져옵니다. 함수가 falsy 값을 던지거나 반환하면 SWR은 요청을 시작하지 않습니다.

// 조건부 가져오기
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)

의존

SWR은 다른 데이터에 의존하는 데이터를 가져오는 것 또한 허용합니다. 이는 다음 데이터 가져오기를 위한 동적 데이터 조각이 필요할 때, 직렬 가져오기뿐만 아니라 가능한 최대 병렬 처리(폭포수 방지)를 보장합니다.

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'
}

인자

기본값으로, key는 인자로써 fetcher에 전달됩니다. 따라서 다음 세 가지 표현식은 동일합니다.

useSWR('/api/user', () => fetcher('/api/user'))
useSWR('/api/user', url => fetcher(url))
useSWR('/api/user', fetcher)

다중 인자

fetcher의 다중 인자를 포함하는 배열을 key 파라미터로 사용할 수 있습니다.

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

객체 전달

객체를 키로써 바로 전달할 수 있으며 fetcher가 그 객체를 받습니다.

const { data: orders } = useSWR({ url: '/api/orders', args: user }, fetcher)

뮤테이션(Mutation) & 재검증(Revalidation)

SWR은 원격 데이터 및 관련 캐시를 변경하기 위한 mutate 및 useSWRMutation API를 제공합니다.

mutate

mutate API를 사용하여 데이터를 변경하는 방법에는 모든 키를 변경할 수 있는 global mutate API와 해당 SWR hook의 데이터만 변경할 수 있는 bound mutate API가 있습니다.

Global Mutate

global mutator를 가져오는 권장 방법은 useSWRConfig hook을 사용하는 것입니다.

import { useSWRConfig } from "swr"
 
function App() {
  const { mutate } = useSWRConfig()
  mutate(key, data, options)
}

전역으로 가져올 수도 있습니다.

import { mutate } from "swr"
 
function App() {
  mutate(key, data, options)
}

Bound Mutate

Bound mutate는 현재 키를 기반으로 데이터로 변경하는 빠른 방법입니다. useSWR 함수에 전달된 키와 연결된 키는 캐시에서 데이터를 찾을 때 사용되며, 이렇게 찾은 데이터는 첫 번째 인자로 반환됩니다.

이전 섹션의 global mutate 함수와 기능적으로 동일하지만 key 매개변수가 필요하지 않습니다.

import useSWR from 'swr'
 
function Profile () {
  const { data, mutate } = useSWR('/api/user', fetcher)
 
  return (
    <div>
      <h1>My name is {data.name}.</h1>
      <button onClick={async () => {
        const newName = data.name.toUpperCase()
        // API에 대한 요청을 종료하여 데이터를 업데이트 합니다.
        await requestUpdateUsername(newName)
        // 로컬 데이터를 즉시 업데이트 하고 다시 유효성 검사(refetch)를 합니다.
        // NOTE: key는 미리 바인딩되어 있으므로 useSWR의 mutate를 사용할 때 필요하지 않습니다.
        mutate({ ...data, name: newName })
      }}>Uppercase my name!</button>
    </div>
  )
}

재검증

데이터 없이 mutate(key) (또는 바인딩된 mutate API로 mutate())를 호출하면, 리소스에 대한 재검증(데이터를 만료된 것으로 표시하고 refetch를 트리거) 합니다. 이 예제는 사용자가 "로그아웃" 버튼을 클릭할 때 로그인 정보(예: 내부)를 자동으로 다시 가져오는 방법을 보여줍니다.

import useSWR, { useSWRConfig } from 'swr'
 
function App () {
  const { mutate } = useSWRConfig()
 
  return (
    <div>
      <Profile />
      <button onClick={() => {
        // 쿠키를 만료된 것으로 설정
        document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
 
        // `/api/user` 라는 키를 가진 모든 SWR에게 재검증을 지시합니다.
        mutate('/api/user')
      }}>
        Logout
      </button>
    </div>
  )
}

📝 후기

SWR을 조금 더 자세히 알아보았습니다. SWR은 단순히 데이터 가져오기를 위한 React Hooks 역할 뿐만 아니라 재사용 가능하면 자동 및 수동 갱신 기능들으로 웹을 더 효율적으로 만들 수있는 도구라고 생각되었습니다. 모든 기능을 사용할 수는 없지만 이번 프로젝트에서 다양하게 쓸 기회가 있으면 좋을 것 같습니다.


🧾 Reference



본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.

#프로젝트캠프 #프로젝트캠프후기 #유데미 #스나이퍼팩토리 #웅진씽크빅 #인사이드아웃 #IT개발캠프 #개발자부트캠프 #리액트 #react #부트캠프 #리액트캠프

profile
기록하고 배우고 시도하고

1개의 댓글

comment-user-thumbnail
2023년 7월 23일

공감하며 읽었습니다. 좋은 글 감사드립니다.

답글 달기