SWR

박상욱·2022년 3월 17일
0

SWR

목록 보기
1/1

SWR?

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

SWR의 주 목적은

데이터를 가져오는 것!

동작 원리

그전에 cache되어있는 것이 있으면 그것을보여주고 cache가 있으면 그것을 share한다 share의 기준은 key다.

  • revalidate 하는동안 CACHE되어있는 데이터를 먼저 보여주고 revalidate 최신화된 데이터를 가져온다는 것이다.

강점 기능

단 한 줄의 코드로 프로젝트 내의 데이터 가져오기 로직을 단순화할 수 있으며, 다음과 같은 모든 놀라운 기능들을 바로 사용할 수도 있습니다.

  • 빠르고, 가볍고, 재사용 가능한 데이터 가져오기
  • 내장된 캐시 및 요청 중복 제거
  • 실시간 경험
  • 전송 및 프로토콜에 구애받지 않음
  • SSR / ISR / SSG support
  • TypeScript 준비
  • React Native
  • SWR은 더 나은 경험을 구축할 수 있도록 속도, 정확성, 안정성의 모든 측면을 다룹니다.
  • 빠른 페이지 네비게이션
  • 인터벌 폴링
  • 데이터 의존성
  • 포커스시 재검증
  • 네트워크 회복시 재검증
  • 로컬 뮤테이션(Optimistic UI)
  • 스마트한 에러 재시도
  • 페이지 및 스크롤 위치 복구
  • React Suspense

공식 DOSC 기본 예제


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

export default 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>;
 }

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

key : 데이터의 고유한 식별자(일반적으로 API URL) -> fetcher로 전달됨
fetcher : 데이터를 반환하는 어떠한 비동기 함수도 될 수 있다.
네이티브 fetch 또는 Axios와 같은 도구를 사용 가능 하다.

useSWR 반환값

요청의 상태에 기반한 data와 error.

예시

Profile.jsx

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

//useSWR을 호출하는 함수
function useUser(id) {
  const { data, error } = useSWR(
    `/api/user/${id}`,
    fetcher
    //  {
    //   refreshInterval: 1000,
    // }
  );

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

//Avatar component
export function Avatar({ id }) {
  const { user, isLoading, isError } = useUser(id);

  if (isLoading) return <div>loading... Avatar</div>;
  if (isError) return <div>failed to load... Avatar</div>;
  return (
    <>
      <div>hello {user.name} ! avatar</div>
    </>
  );
}

//Profile component
function Profile({ id }) {
  const { user, isLoading, isError } = useUser(id);

  if (isLoading) return <div>loading...</div>;
  if (isError) return <div>failed to load</div>;

  //데이터 랜더링
  return (
    <>
      <div>hello {user.name}!</div>
      <Avatar id={123} />
    </>
  );
}


//PageComponent
export const Page = () => {
  return (
    <>
      <Profile id={123} />
      <Avatar id={124} />
    </>
  );
}

예제를 보면 useSWR를 호출하는 useUser 함수를 가지고 있는 Avatar, Profile component를 볼 수 있다.

Profile component는 안에서 Avatar component를 한번더 호출한다.

하지만 찍혀있는 log를 보면 호출하는 부분은 3군데 인데 3번 호출되지 않고 2번만 호출된걸 볼수있다. 그 이유는 url을 share한다는 의미이다.

api key name ->/api/key/124로 유일하다면 응답은 cache해놓은 값을 그대로 내려주기때문에 효율적으로 사용할수 있을것이다.

즉 ! SWR은 여러 컴포넌트에서 같은 api를 여러번 호출하는 것을 방지한다.

SWR의 cache

기본적으로 SWR은 전역 캐시를 사용해 모든 컴포넌트 사이에 데이터를 저장하고 공유합니다. 하지만 SWRConfig의 provider 옵션으로 이 동작을 커스터마이징 할 수도 있습니다


중첩된 경우, SWR hook은 상위 레벨의 캐시 공급자를 사용합니다. 상위 레벨의 캐시 공급자가 존재하지 않을 경우, 빈 Map인 기본 캐시 공급자로 대체됩니다.

캐시 공급자 생성하기

SWRConfigprovider 옵션은 캐시 공급자를 반환하는 함수를 받습니다. 그러면 공급자를 SWRConfig 경계 내의 모든 SWR hook에서 사용할 수 있습니다.

import useSWR, { SWRConfig } from 'swr'

function App() {
  return (
    <SWRConfig value={{ provider: () => new Map() }}>
      <Page/>
    </SWRConfig>
  )
}

캐시를 localStorage와 동기화

function localStorageProvider() {
  
  const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]"));

  // app을 unloading하기 전에, 모든 데이터를 `localStorage`에 다시 씁니다.
  console.log("localStorageProvider");
  window.addEventListener("beforeunload", () => {
    const appCache = JSON.stringify(Array.from(map.entries()));
    localStorage.setItem("app-cache", appCache);
  });

  // 성능을 위해 여전히 map을 사용해 쓰고 읽습니다.
  return map;
}

export default function Cache() {
  //Avatar가 useUser라는 훅을 쓰는데 거기는 config 옵션이 하나도 안들어가있다.
  //Page에 감싸놓으면 option으로 동작한다. provider가 provider를 감싸고 있는 형국
  return (
    <SWRConfig
      value={{ refreshInterval: 1000, provider: localStorageProvider }}
    >
      <Page />
    </SWRConfig>
  );

초기화할 때, localStorage의 데이터를 map으로 복구 후 unloading전 모든 데이터를 localStorage에 다시 사용.

cache와 persistent 예제

function localStorageProvider() {
  // 초기화할 때, `localStorage`의 데이터를 map으로 복구합니다.
  const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]"));

  // app을 unloading하기 전에, 모든 데이터를 `localStorage`에 다시 씁니다.
  console.log("localStorageProvider");
  window.addEventListener("beforeunload", () => {
    const appCache = JSON.stringify(Array.from(map.entries()));
    localStorage.setItem("app-cache", appCache);
  });

  // 성능을 위해 여전히 map을 사용해 쓰고 읽습니다.
  return map;
}

export default function Cache() {
  //Avatar가 useUser라는 훅을 쓰는데 거기는 config 옵션이 하나도 안들어가있다.
  //Page에 감싸놓으면 option으로 동작한다. provider가 provider를 감싸고 있는 형국
  return (
    <SWRConfig
      value={{ refreshInterval: 1000, provider: localStorageProvider }}
    >
      <Page />
    </SWRConfig>
  );
}

const Page = () => {
  const { cache, mutate } = useSWRConfig(); //SWRConfig로 감싸준 덕분에 cache에 직접 도달할수있음.
  console.log("cache", cache);
  return (
    <div>
      <Avatar id={1212} />
      <button
        onClick={() => {
          mutate("/api/user/1212"); // -> 이키로 저장되어있는 fetcher를 다시 실행한다.
          //이키로 데이터뿐만아니라 fetcher도 저장되어있어 새로운값을 불러오는데 사용할수있다.
          console.log(`check : ${JSON.stringify(cache.get("/api/user/1212"))}`); // cache안에 key값으로 객체를 끌어올수 있다.
        }}
      >
        check
      </button>
    </div>
  );
};
profile
개발자

0개의 댓글