Front-end/React

[SWR] 데이터 가져오기를 위한 React Hooks

hyebin Lee 2022. 7. 30. 22:15

swr로 전역적인 상태 관리하기

프론트에서 데이터를 전역으로 저장하고 싶으면 리덕스가 필요하다. 하지만 리덕스에도 단점이 있다. 리덕스는 코드양이 많고, 한번 가져왔던 정보여도 컴포넌트가 마운트 될 때마다 새로 가져와야하며, 지속적으로 리덕스 로컬 스토어의 상태를 서버 상태와 맞추기 위한 동기화 작업이 필요하다. 따라서 리덕스를 사용하지 않고 그 대안으로 swr을 사용해 보고자 한다. 

 

SWR은 데이터 가져오기를 위한 React Hooks 라이브러리로 먼저 캐시(스태일)로부터 데이터를 반환한 후, fetch 요청(재검증)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략이다. 

SWR을 사용하면 컴포넌트는 지속적이며 자동으로 데이터 업데이트 스트림을 받게 되며
그리고 UI는 항상 빠르고 반응적이다.

import useSWR from 'swr'

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

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

useSWR hook은 key 문자열과 fetcher 함수를 받는다. 

key는 데이터의 고유한 식별자이며(일반적으로 API URL) fetcher로 전달 되는데, 여기의 fetcher는 SWR의 key를 받고 데이터를 반환하는 비동기 함수로,  반환된 값은 data로 전달되며, 만약 throws라면 error로 잡힌다.

fetch 프로미스가 거부되면 error 객체가 정의된다. 

 

useSWR의 기본 구성

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

key: unique한 key값으로 string 또는 function, array, null 등이 올 수 있다.

fetcher: data를 fetch하여 넘어온 Promise를 말한다.

options: SWR hook의 옵션

data : fetcher에 의해 반환된 data.

error: fetcher에서 던진 오류입니다. 성공 시에는 undefined

isValidating : 만약 요청이 있거나 로딩 중인 경우에 반환 ->  boolean

mutate(data? shouldRevalidate?) : 캐시된 데이터를 mutate하기 위한 함수

 

 

사용법 ? fetcher 함수 생성 (데이터 가져오기)

import fetch from 'unfetch'

const fetcher = url => fetch(url).then(r => r.json())

function App () {
  const { data, error } = useSWR('/api/data', fetcher)
  // ...
}
import axios from 'axios'

const fetcher = url => axios.get(url).then(res => res.data)

function App () {
  const { data, error } = useSWR('/api/data', fetcher)
  // ...
}

 

자동갱신

페이지에 다시 포커스하거나 탭을 전환할 때, SWR은 자동으로 데이터를 갱신한다. (이 기능은 기본적으로 활성화)

재 연결시 갱신 (이 기능은 기본적으로 활성화)

최신 상태로 즉시 동기화할 수 있어 오래된 모바일 탭 또는 슬립 모드로 빠진 노트북과 같은 시나리오에서 데이터를 새로 고치는데 유용하다. 

refreshInterval 값을 설정하여 시간을 정해 자동 갱신이 가능하다.

useSWR('/api/todos', fetcher, { refreshInterval: 1000 })

 

중복제거

function useUser () {
  return useSWR('/api/user', fetcher)
}

function Avatar () {
  const { data, error } = useUser()

  if (error) return <Error />
  if (!data) return <Spinner />

  return <img src={data.avatar_url} />
}

function App () {
  return <>
    <Avatar />
    <Avatar />
    <Avatar />
    <Avatar />
    <Avatar />
  </>
}

각각의 <Avatar> 컴포넌트는 내부에 useSWR hook을 갖는다. 이들이 동일한 SWR 키를 갖고 있으므로 거의 동시에 렌더링 되며 단 한 번의 네트워크 요청만 발생한다.

성능이나 중복된 요청에 대한 걱정 없이 위 예시의 useUser와 같은 데이터 hook을 어디에서든 재사용할 수 있다.

기본 중복 제거 인터벌을 오버라이딩할 수 있는 dedupingInterval 옵션도 있다.

 

 

useSWR 훅 만들기

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

  return {
    user: data,
    isLoading: !error && !data,
    isError: error
  }
}
// 사용법
function UserProfile({ id }) {
  const { user, isLoading, isError } = useUser(id)

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

 

mutation

useSWRConfig로부터 mutate 함수를 얻을 수 있으며, 이때의 mutate(key)를 호출해 동일한 키를 사용한 다른 SWR hook에게 갱신 메시지를 전역으로 브로드캐스팅할 수 있다. 

mutate를 사용하면 갱신하고 최종적으로 최신 데이터로 이를 대체하는 동안에 로컬 데이터를 프로그래밍 방식으로 업데이트할 수 있다.

import useSWR, { useSWRConfig } from 'swr'

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

  return (
    <div>
      <h1>My name is {data.name}.</h1>
      <button onClick={async () => {
        const newName = data.name.toUpperCase()
        
        // 로컬 데이터를 즉시 업데이트하지만, 갱신은 비활성화
        mutate('/api/user', { ...data, name: newName }, false)
        
        // 소스 업데이트를 위해 API로 요청 전송
        await requestUpdateUsername(newName)
        
        // 로컬 데이터가 올바른지 확인하기 위해 갱신(refetch) 트리거
        mutate('/api/user')
      }}>Uppercase my name!</button>
    </div>
  )
}

위 예시에서 버튼을 클릭하면 클라이언트 데이터를 로컬에서 업데이트하고, 리모트 데이터를 수정하기 위한 POST 요청을 보내고 최신 데이터를 가져오기 위한 시도를 한다.(갱신).

하지만 많은 POST API들은 업데이트된 데이터를 직접 반환하기 때문에 다시 갱신할 필요가 없다. 다음 예시는 “로컬 뮤테이트 - 요청 - 업데이트” 사용법을 보여준다.

mutate('/api/user', newUser, false)             // 갱신 없이 뮤테이트하기 위해 `false` 사용
mutate('/api/user', updateUser(newUser), false) // `updateUser`는 요청의 Promise입니다.
                                                // 업데이트된 문서를 반환합니다.

 mutate의 인자 중 첫 번째는 mutate할 대상, 두 번째 인자는 shouldRevalidate 속성에 대한 값을 설정하는 것이다. 공란이라면 기본적으로 true 설정이 되어있으므로 정말 업데이트를 하지 않으려면 false로 해줘야 한다.

 

추가적으로 mutate truef를 이용하면 Optimistic UI가 가능하다. 

먼저 성공할 것이라고 간주하고 나중에 점검하는 것을 Optimistic UI라고 한다.

Optimistic UI의 경우 실제로 바로바로 업데이트를 해 주지는 않지만, 사용자 입장에서는 바로바로 업데이트가 되는 것으로 착각하게끔 할 수 있다. UX의 입장에서 굉장히 좋은 것이다.

반면에 Pessimistic UI도 있는데, 이는 즉시 서버에 요청부터 하고, 그 이후에 이상이 없을 경우 반영하는 UI이다.

 

 

 

참고자료 : https://swr.vercel.app/ko