"SWR"이라는 이름은 HTTP RFC 5861에 의해 알려진 HTTP 캐시 무효 전략인 stale-while-revalidate에서 유래되었습니다. SWR은 먼저
캐시(스태일)
로부터데이터를 반환
한 후,fetch 요청( revalidate:재검증)
을 하고,최종적으로 최신화된 데이터를 가져오는 전략
입니다.
먼저 CACHE로 부터 데이터를 반환한다. 그 다음에 요청을 한 후 최종적으로 최신화된 데이터를 가져오는 전략이다.
데이터를 가져오는 것!
그전에 cache되어있는 것이 있으면 그것을보여주고 cache가 있으면 그것을 share한다 share의 기준은 key다.
- revalidate 하는동안 CACHE되어있는 데이터를 먼저 보여주고 revalidate 최신화된 데이터를 가져온다는 것이다.
단 한 줄의 코드로 프로젝트 내의 데이터 가져오기 로직을 단순화할 수 있으며, 다음과 같은 모든 놀라운 기능들을 바로 사용할 수도 있습니다.
- 빠르고, 가볍고, 재사용 가능한 데이터 가져오기
- 내장된 캐시 및 요청 중복 제거
- 실시간 경험
- 전송 및 프로토콜에 구애받지 않음
- SSR / ISR / SSG support
- TypeScript 준비
- React Native
- SWR은 더 나은 경험을 구축할 수 있도록 속도, 정확성, 안정성의 모든 측면을 다룹니다.
- 빠른 페이지 네비게이션
- 인터벌 폴링
- 데이터 의존성
- 포커스시 재검증
- 네트워크 회복시 재검증
- 로컬 뮤테이션(Optimistic UI)
- 스마트한 에러 재시도
- 페이지 및 스크롤 위치 복구
- React Suspense
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은 전역 캐시를 사용해 모든 컴포넌트 사이에 데이터를 저장하고 공유합니다. 하지만 SWRConfig의 provider 옵션으로 이 동작을 커스터마이징 할 수도 있습니다
중첩된 경우, SWR hook은 상위 레벨의 캐시 공급자를 사용합니다. 상위 레벨의 캐시 공급자가 존재하지 않을 경우, 빈 Map인 기본 캐시 공급자로 대체됩니다.
SWRConfig
의 provider 옵션
은 캐시 공급자를 반환하는 함수를 받습니다. 그러면 공급자를 SWRConfig 경계 내의 모든 SWR hook에서 사용할 수 있습니다
.
import useSWR, { SWRConfig } from 'swr'
function App() {
return (
<SWRConfig value={{ provider: () => new Map() }}>
<Page/>
</SWRConfig>
)
}
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>
);
};