"SWR"이라는 이름은 HTTP RFC 5861에 의해 알려진 HTTP 캐시 무효 전략인 stale-while-revalidate에서 유래되었습니다. SWR은 먼저 캐시(스태일)로부터 데이터를 반환한 후, fetch 요청(재검증)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략입니다.
swr을 시작하기전에 msw와 axios를 설치해주자
npm install axios
npm install msw
npm install swr
handler.js
import { rest } from "msw";
export const handlers = [
rest.get("http://localhost:3000/api/user/123", async (req, res, ctx) => {
return res(
ctx.json({
name: "park",
})
);
}),
];
browser.js
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
//worker를 실행하는 코드
if (process.env.NODE_ENV === "development") {
const { worker } = require("./mocks/browser");
worker.start();
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
npx msw init public/ --save
위 명령어를 통해 public 폴더 생성
여기서 worker를 실행하는 코드를 넣어주지 않으면 아무리 api url 같아도 404 error 발생
profile.jsx
import axios from 'axios'
import React from 'react'
import useSWR from 'swr'
const fetcher = (...args) => axios.get(...args).then((res)=>res.data);
export default 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>
<div>hello{data.name}!</div>
</div>
)
}
브라우저내에 포커스 가게되면 다시 한번 api를 호출하게 된다.
Profile.jsx
import axios from 'axios'
import React from 'react'
import useSWR from 'swr'
const fetcher = (...args) => axios.get(...args).then((res)=>res.data);
function useUser (id) {
const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
return {
user: data,
isLoading,
isError: error
}
}
export default function Page() {
return(<div>
<Profile id={1234}/>
<Avatar id={1235}/>
</div>)
}
function Profile({id}) {
const { user, error, isLoading } = useUser(id);
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return (
//데이터 렌더링
<div>
<div>hello{user.name}!</div>
<Avatar id={123}/>
</div>
)
}
function Avatar({id}){
const { user, error, isLoading } = useUser(id);
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return (
//데이터 렌더링
<div>
<div>hello{user.name}(Avatar)</div>
</div>
)
}
코드에서 보는 것처럼 component가 3개 존재하지만 api는 2번 호출한다. 유일한 키가 유일하다면 똑같은 url로 들어오면 캐쉬해놓은 값이 들어오게 된다. 서로 값들을 공유하고 있다.
불필요한 api 호출을 방지한다.
const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher, {refreshInterval:1000})
refreshInterval을 통해 지정한 시간이 되었을 때 자동으로 api를 재요청한다.
Cache.jsx
import React from 'react'
import { SWRConfig, useSWRConfig } from 'swr'
import {Avatar} from "./Profile";
export default function Cache() {
return (
<SWRConfig>
<Page />
</SWRConfig>
)
}
const Page = () =>{
const {cache,mutate} = useSWRConfig();
return (
<div>
<Avatar id={1212}/>
<button onClick={()=>{
mutate("/api/user/1212")
console.log(`click:${JSON.stringify(cache.get("/api/user/1212"))}`);
}}>체크</button>
</div>
)
}
mutate는 저장되어있는 fetch를 다시 호출한다.
import React from 'react'
import { SWRConfig, useSWRConfig } from 'swr'
import {Avatar} from "./Profile";
function localStorageProvider(){
const map = new Map(JSON.parse(localStorage.getItem('app-cache') || "[]"));
window.addEventListener('beforeunload', () =>{
const appCache =JSON.stringify(Array.from(map.entries()))
localStorage.setItem('app-cache',appCache);
})
return map;
}
export default function Cache() {
return (
<SWRConfig value={{refreshInterval:1000,provider:localStorageProvider}}>
<Page />
</SWRConfig>
)
}
const Page = () =>{
const {cache,mutate} = useSWRConfig();
return (
<div>
<Avatar id={1212}/>
<button onClick={()=>{
mutate("/api/user/1212")
console.log(`click:${JSON.stringify(cache.get("/api/user/1212"))}`);
}}>체크</button>
</div>
)
}
import axios from 'axios'
import React from 'react'
import useSWR,{ SWRConfig, useSWRConfig } from 'swr'
export default function Mutate() {
return (
<SWRConfig>
<Page/>
<Profile/>
</SWRConfig>
)
}
const fetcher = (url) => axios.get(url).then((res)=>res.data) ;
const Page = () =>{
const {data} = useSWR("/api/user/123",fetcher);
const{mutate} = useSWRConfig();
if(!data){
return <div>loading...</div>
}
return (
<div>
<h1> My name is {data.name}</h1>
<button onClick={async () =>{
const newName = data.name.toUpperCase();
mutate("/api/user/123",{...data,name:newName},false);
mutate("api/user/123");
}}>
My name
</button>
</div>
)
}
const Profile = () =>{
const {data,mutate} = useSWR("/api/user/124",fetcher);
if(!data){
return <div>loading...</div>
}
return (
<div>
<h1> My name is {data.name}</h1>
<button onClick={async () =>{
const newName = data.name.toUpperCase();
mutate({...data,name:newName},false);
mutate();
}}>
My name
</button>
</div>
)
}