Skip to content
J127_์šฐ์žฌ์„ edited this page Nov 25, 2021 · 1 revision

SWR์ด๋ž€?

The name โ€œSWRโ€ is derived fromย stale-while-revalidate, a HTTP cache invalidation strategy popularized byย HTTP RFC 5861. SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data.

React Hooks for Data Fetching - SWR

์ฆ‰, cache๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € ๋ฆฌํ„ดํ•˜๊ณ  ๊ทธ ๋‹ค์Œ์— fetch๋ฅผ ํ†ตํ•ด ์—…๋ฐ์ดํŠธ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ํ˜•์‹์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋กœ์ง์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์กฐ๊ธˆ ๋” ์ข‹์€ UX๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

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?): ์บ์‹œ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฎคํ…Œ์ดํŠธํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜

์˜ต์…˜

  • fetcher(args): fetcher ํ•จ์ˆ˜
  • revalidateOnFocus = true: ์ฐฝ์ด ํฌ์ปค์‹ฑ๋˜์—ˆ์„ ๋•Œ ์ž๋™
  • revalidateOnReconnect = true: ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ๋‹ค์‹œ ์–ป์—ˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ๊ฐฑ์‹ (navigator.onLine์„ ํ†ตํ•ด)
  • refreshInterval = 0: ์ธํ„ฐ๋ฒŒ ํด๋ง(๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ๋น„ํ™œ์„ฑํ™”)
  • refreshWhenHidden = false: ์ฐฝ์ด ๋ณด์ด์ง€ ์•Š์„ ๋•Œ ํด๋ง(refreshInterval์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ)
  • refreshWhenOffline = false: ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์˜คํ”„๋ผ์ธ์ผ ๋•Œ ํด๋ง(navigator.onLine์— ์˜ํ•ด ๊ฒฐ์ •๋จ)
  • shouldRetryOnError = true: fetcher์— ์—๋Ÿฌ๊ฐ€ ์žˆ์„ ๋•Œ ์žฌ์‹œ๋„
  • dedupingInterval = 2000: ์ด ์‹œ๊ฐ„ ๋ฒ”์œ„๋‚ด์— ๋™์ผ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์š”์ฒญ ์ค‘๋ณต ์ œ๊ฑฐ
  • focusThrottleInterval = 5000: ์ด ์‹œ๊ฐ„ ๋ฒ”์œ„ ๋™์•ˆ ๋‹จ ํ•œ ๋ฒˆ๋งŒ ๊ฐฑ์‹ 
  • errorRetryInterval = 5000: ์—๋Ÿฌ ์žฌ์‹œ๋„ ์ธํ„ฐ๋ฒŒ
  • errorRetryCount: ์ตœ๋Œ€ ์—๋Ÿฌ ์žฌ์‹œ๋„
  • ๊ธฐํƒ€ ๋“ฑ๋“ฑ.... ๋” ๋งŽ๋‹ค.

๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

์ฒซ๋ฒˆ์งธ ์ธ์ž๋Š” key์— ๋Œ€ํ•œ ํ•ญ๋ชฉ์œผ๋กœ ๋‘๋ฒˆ์งธ ์ธ์ž์ธ fetcher์—๊ฒŒ ์ „๋‹ฌ๋˜๋Š” ์ธ์ž๋กœ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ๊ณผ ๋™์‹œ์— SWR ์š”์ฒญ์˜ ๊ตฌ๋ถ„ ์ธ์ž๋กœ๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต URL์™€ ๊ฐ™์€ ๋‚ด์šฉ์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

๋‘๋ฒˆ์งธ ์ธ์ž๋Š” fetcher์— ๋Œ€ํ•œ ํ•ญ๋ชฉ์œผ๋กœ Data๋ฅผ Fetchํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต fetch๋‚˜ axios๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์ œ

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

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

๋‘๋ฒˆ์งธ ์ธ์ž์ธ fetcher๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋ฉด ํ•ด๋‹น ์‘๋‹ต์ด data๋กœ ์„ธํŒ…๋˜๊ณ , ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ error๋กœ ์„ธํŒ…๋ฉ๋‹ˆ๋‹ค.

์ž๋™ ๊ฐฑ์‹ 

ํฌ์ปค์Šค ์‹œ์— ๊ฐฑ์‹ ํ•˜๊ธฐ

SWR์€ ํŽ˜์ด์ง€์— ๋‹ค์‹œ ํฌ์ปค์Šคํ•˜๊ฑฐ๋‚˜ ํƒญ์„ ์ „ํ™˜ํ•  ๋•Œ, ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.

SWR์˜ revalidateOnFocus ์˜ต์…˜์„ ํ†ตํ•ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ธํ„ฐ๋ฒŒ ์‹œ์— ๊ฐฑ์‹ ํ•˜๊ธฐ

SWR์˜ refreshInterval์„ ์ด์šฉํ•˜์—ฌ ๋งค ์‹œ๊ฐ„๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useSWR('/api/todos', fetcher, { refreshInterval: 1000 }) // 1์ดˆ๋งˆ๋‹ค ์—…๋ฐ์ดํŠธ

ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ ์ธํ”ผ๋‹ˆํ‹ฐ ๋กœ๋”ฉ

๊ธฐ์กด์˜ SWR์˜ useSWR์„ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ ์ธํ”ผ๋‹ˆํ‹ฐ ๋กœ๋”ฉ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, SWR์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” useSWRInfinite Hook์„ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

API

  • getKey: ์ธ๋ฑ์Šค์™€ ์ด์ „ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ณ  ํŽ˜์ด์ง€์˜ ํ‚ค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
  • fetcher:ย useSWR์˜ย fetcherํ•จ์ˆ˜์™€ ๋™์ผ
  • options:ย useSWR์ด ์ง€์›ํ•˜๋Š” ๋ชจ๋“  ์˜ต์…˜์„ ๋ฐ›์Œ. ์„ธ ๊ฐœ์˜ ์ถ”๊ฐ€ ์˜ต์…˜์„ ํฌํ•จ:
    • initialSize = 1: ์ดˆ๊ธฐ์— ๋กœ๋“œํ•ด์•ผ ํ•˜๋Š” ํŽ˜์ด์ง€์˜ ์ˆ˜
    • revalidateAll = false: ํ•ญ์ƒ ๋ชจ๋“  ํŽ˜์ด์ง€์˜ ๊ฐฑ์‹  ์‹œ๋„
    • persistSize = false: ์ฒซ ํŽ˜์ด์ง€์˜ ํ‚ค๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ, ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ 1(initialSize๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐย initialSize)๋กœ ์ดˆ๊ธฐํ™”ํ•˜์ง€ ์•Š์Œ

๋ฐ˜ํ™˜ ๊ฐ’

  • data: ๊ฐ ํŽ˜์ด์ง€์˜ ๊ฐ€์ ธ์˜ค๊ธฐ ์‘๋‹ต ๊ฐ’์˜ ๋ฐฐ์—ด
  • error:ย useSWR์˜ย error์™€ ๋™์ผ
  • isValidating:ย useSWR์˜ย isValidating๊ณผ ๋™์ผ
  • mutate:ย useSWR์˜ ๋ฐ”์ธ๋”ฉ ๋œ ๋ฎคํ…Œ์ดํŠธ ํ•จ์ˆ˜์™€ ๋™์ผํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์„ ๋‹ค๋ฃธ
  • size: ๊ฐ€์ ธ์˜ฌย ํŽ˜์ด์ง€ ๋ฐ ๋ฐ˜ํ™˜๋ ย ํŽ˜์ด์ง€์˜ ์ˆ˜
  • setSize: ๊ฐ€์ ธ์™€์•ผ ํ•˜๋Š” ํŽ˜์ด์ง€์˜ ์ˆ˜๋ฅผ ์„ค์ •
const getKey = (pageIndex, previousPageData) => {
  if (previousPageData && !previousPageData.length) return null // ๋์— ๋„๋‹ฌ
  return `/users?page=${pageIndex}&limit=10`                    // SWR ํ‚ค
}

function App () {
  const { data, size, setSize } = useSWRInfinite(getKey, fetcher)
  if (!data) return 'loading'

  return <div>
    {data.map((users, index) => {
      // `data`๋Š” ๊ฐ ํŽ˜์ด์ง€์˜ API ์‘๋‹ต ๋ฐฐ์—ด์ž…๋‹ˆ๋‹ค.
      return users.map(user => <div key={user.id}>{user.name}</div>)
    })}
    <button onClick={() => setSize(size + 1)}>Load More</button>
  </div>
}

๊ธฐ์กด์˜ useSWR๊ณผ useSWRInfinite ๊ฐ€์žฅ ํฌ๊ฒŒ ๋‹ค๋ฅธ ์ ์€ getKey ํ•จ์ˆ˜์™€ ๋ฐ˜ํ™˜๋˜๋Š” data์˜ ํ˜•ํƒœ์ด๋‹ค.

getKey ํ•จ์ˆ˜๊ฐ€ ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ์ธ๋ฑ์Šค์™€ ์ด์ „ ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ธ์ž๋กœ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— useSWR์— ๋ณด๋‹ค ์ธ๋ฑ์Šค ๊ธฐ๋ฐ˜ ๋ฐ ์ปค์„œ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์‰ฝ๊ฒŒ ์ง€์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ์‘๋‹ต๋ฐ›๋Š” data๊ฐ€ ํ•˜๋‚˜์˜ API ์‘๋‹ต์ด ์•„๋‹Œ ์—ฌ๋Ÿฌ API์˜ ์‘๋‹ต์˜ ๋ฐฐ์—ด ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค.

// ex) ๋ฐ˜ํ™˜๋˜๋Š” data ํ˜•ํƒœ => 2์ฐจ์› ๋ฐฐ์—ด
[
  [
    { name: 'Alice', ... },
    { name: 'Bob', ... },
    { name: 'Cathy', ... },
    ...
  ],
  [
    { name: 'John', ... },
    { name: 'Paul', ... },
    { name: 'George', ... },
    ...
  ],
  ...
]

๐Ÿ“– ๊ฐœ๋ฐœ๋ฌธ์„œ

๐Ÿšฅ ๊ทœ์น™

๐Ÿค” ์Šคํ”„๋ฆฐํŠธ ํšŒ์˜

๐Ÿ“” ํ•™์Šต

๐Ÿ•™ ๋ฐ์ผ๋ฆฌ ์Šคํฌ๋Ÿผ

๐Ÿ’ญ ํšŒ๊ณ ๋ก

๐Ÿ‘จโ€๐Ÿ‘ฆ ๋ฉ˜ํ† ๋ง

๋ฐ๋ชจ์˜์ƒ

Clone this wiki locally