SWR : React 상태 관리 및 캐시 관리

Kim jae-eok·2021년 9월 11일
0

시작하기

설치

React 프로젝트 폴더 안에서 다음을 실행하세요.

yarn add swr

npm을 사용한다면

npm install swr

빠른 시작

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

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

GraphQL API 또는 Axios와 같은 라이브러리를 사용하려면 여러분만의 fetcher 함수를 생성하면 됩니다. 여기에서 더 많은 예시를 확인하세요.

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

import useSWR from 'swr'

function Profile () {
  const { data, error } = useSWR('/api/user/123', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  // 데이터 렌더링
  return <div>hello {data.name}!</div>
}

일반적으로, 세 가지 요청 상태가 가능합니다: "loading", "ready", "erorr". data와 error 값을 사용해 현재 요청의 상태를 알아내고, 해당하는 UI를 반환할 수 있습니다.

재사용 가능하게 만들기

웹 앱을 구축할 때, UI의 많은 곳에서 데이터를 재사용할 필요가 있을 것입니다. SWR 위에서는 재사용 가능한 데이터 hook을 만드는 것이 믿을 수 없을 정도로 쉽습니다.

function useUser (id) {
  const { data, error } = useSWR(`/api/user/${id}`, fetcher)

  return {
    user: data,
    isLoading: !error && !data,
    isError: error
  }
}

그리고 컴포넌트에서 사용합니다.

function Avatar ({ id }) {
  const { user, isLoading, isError } = useUser(id)

  if (isLoading) return <Spinner />
  if (isError) return <Error />
  return <img src={user.avatar} />
}

이 패턴을 적용함으로써 명령형 방식으로 데이터를 가져오는 것에 대해 잊을 수 있습니다: 요청을 시작, 로딩 상태를 업데이트, 최종 결과를 반환. 대신, 코드는 더 선언적입니다: 컴포넌트에서 사용되는 데이터가 무엇인지만 명시하면 됩니다.

예시

실제 예시로, 저희 웹 사이트는 모두 user에 의존하는 navbar와 그 콘텐츠를 보여줍니다.

전통적으로는 최상위 레벨 컴포넌트에서 useEffect를 사용해 데이터를 한 번 가져오고, 이를 props를 통해 자식 컴포넌트에 전달합니다(현재는 에러 상태를 처리하지 않습니다)

// 페이지 컴포넌트

function Page () {
  const [user, setUser] = useState(null)

  // 데이터 가져오기
  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => setUser(data))
  }, [])

  // 전역 로딩 상태
  if (!user) return <Spinner/>

  return <div>
    <Navbar user={user} />
    <Content user={user} />
  </div>
}
// 자식 컴포넌트

function Navbar ({ user }) {
  return <div>
    ...
    <Avatar user={user} />
  </div>
}

function Content ({ user }) {
  return <h1>Welcome back, {user.name}</h1>
}

function Avatar ({ user }) {
  return <img src={user.avatar} alt={user.name} />
}

보통 최상위 레벨 컴포넌트에서 가져온 모든 데이터를 유지하고 트리 아래의 모든 자식 컴포넌트의 props로 추가해야 합니다. 페이지에 더 많은 데이터 의존성을 추가한다면 코드는 점점 유지하기가 힘들어집니다.

Context를 사용하여 props 전달을 피할 수 있습니다만 동적 콘텐츠 문제가 여전히 존재합니다: 페이지 콘텐츠 내 컴포넌트들은 동적일 수 있으며, 최상위 레벨 컴포넌트는 그 자식 컴포넌트가 필요로하는 데이터가 무엇인지 알 수 없을 수도 있습니다.

SWR은 이 문제를 완벽하게 해결합니다. 우리가 막 생성한 useUser hook을 사용해 다음과 같이 리팩토링할 수 있습니다.

// 페이지 컴포넌트

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

데이터는 이제 데이터가 필요한 컴포넌트로 범위가 제한되었으며 모든 컴포넌트는 서로에게 독립적입니다. 모든 부모 컴포넌트들은 데이터나 데이터 전달에 관련된 것들을 알 필요가 없습니다. 그냥 렌더링할 뿐입니다. 코드는 이제 유지하기에 더 간단하고 쉽습니다.

가장 아름다운 것은 이들이 동일한 SWR 키를 사용하며 그 요청이 자동으로 중복 제거, 캐시, 공유되므로, 단 한 번의 요청만 API로 전송된다는 것입니다.

또한, 애플리케이션은 이제 사용자 포커스나 네트워크 재연결 시에 데이터를 갱신할 수 있습니다! 이는 사용자의 노트북이 슬립으로부터 깨어나거나 브라우저 탭을 전환할 때 자동으로 데이터가 갱신된다는 것을 의미합니다.

import Callout from 'nextra-theme-docs/callout'

API 옵션

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

파라미터

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

반환 값

  • data: fetcher가 이행한 주어진 키에 대한 데이터(로드되지 않았다면 undefined)
  • error: fetcher가 던진 에러(또는 undefined)
  • isValidating: 요청이나 갱신 로딩의 여부
  • mutate(data?, shouldRevalidate?): 캐시 된 데이터를 뮤테이트하기 위한 함수 (상세내용)

옵션

  • suspense = false: React Suspense 모드를 활성화 (상세내용)
  • fetcher = window.fetch(url).then(res => res.json()): fetcher 함수
  • revalidateIfStale = true: automatic revalidation on mount even if there is stale data (details)
  • revalidateOnMount: 컴포넌트가 마운트되었을 때 자동 갱신 활성화 또는 비활성화
  • revalidateOnFocus = true: 창이 포커싱되었을 때 자동 갱신 (상세내용)
  • revalidateOnReconnect = true: 브라우저가 네트워크 연결을 다시 얻었을 때 자동으로 갱신(navigator.onLine을 통해) (상세내용)
  • refreshInterval = 0: 인터벌 폴링(기본적으로는 비활성화) (상세내용)
  • refreshWhenHidden = false: 창이 보이지 않을 때 폴링(refreshInterval이 활성화된 경우)
  • refreshWhenOffline = false: 브라우저가 오프라인일 때 폴링(navigator.onLine에 의해 결정됨)
  • shouldRetryOnError = true: fetcher에 에러가 있을 때 재시도
  • dedupingInterval = 2000: 이 시간 범위내에 동일 키를 사용하는 요청 중복 제거
  • focusThrottleInterval = 5000: 이 시간 범위 동안 단 한 번만 갱신
  • loadingTimeout = 3000: onLoadingSlow 이벤트를 트리거 하기 위한 타임아웃
  • errorRetryInterval = 5000: 에러 재시도 인터벌
  • errorRetryCount: 최대 에러 재시도 수
  • fallback: a key-value object of multiple fallback data (example)
  • fallbackData: 반환될 초기 데이터(노트: hook 별로 존재)
  • onLoadingSlow(key, config): 요청을 로드하는 데 너무 오래 걸리는 경우의 콜백 함수(loadingTimeout을 보세요)
  • onSuccess(data, key, config): 요청이 성공적으로 종료되었을 경우의 콜백 함수
  • onError(err, key, config): 요청이 에러를 반환했을 경우의 콜백 함수
  • onErrorRetry(err, key, config, revalidate, revalidateOps): 에러 재시도 핸들러
  • compare(a, b): 비논리적인 리렌더러를 회피하기 위해 반환된 데이터가 변경되었는지를 감지하는데 사용하는 비교 함수. 기본적으로 dequal을 사용합니다.
  • isPaused(): 갱신의 중지 여부를 감지하는 함수. true가 반환될 경우 가져온 데이터와 에러는 무시합니다. 기본적으로는 false를 반환합니다.
  • use: array of middleware functions (details)
느린 네트워크(2G, {'<='} 70Kbps)에서는, 기본적으로 errorRetryInterval이 10초이며, loadingTimeout은 5초입니다.

전역 설정을 사용해 기본 옵션을 제공할 수도 있습니다.

profile
블로그 이전 중 (https://www.notion.so/My-blog-0d569b9028434fb6a99a3e66b6e807b1)

0개의 댓글