요약
SWR이라는 데이터 패치 라이브러리를 도입하여 외부에서 데이터를 불러올 때 로딩, 에러, 데이터를 단순하게 관리하고, 7페이지에서 총 순 93줄을 줄였다.
상황
인턴으로 간 회사에서 코드를 보니 매 페이지마다 외부 요청 페이지에서 요청, 로딩, 에러를 일일이 useState, fetch로 구현하고 있었다.
해결
SWR 라이브러리를 도입하였다.
SWR란?
SWR은 데이터 패치 리액트 훅이다.
Stale-While-Revalidate이라는 HTTP 캐시 무효 전략을 기반으로 만들어진 라이브러리로, Stale-While-Revalidate은 우선 캐시된 데이터를 반환하면서 데이터를 요청하고, 응답받으면 이를 최신화하는 전략이다.
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then((res) => res.json());
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
공식 문서에 나온 간단한 예제이다.
useSWR을 통해 원하는 url과 패처를 지정해 주면, 그에 대한 응답, 에러, 로딩처리를 해준다.
SWR 채택 이유
데이터 패치 라이브러리로 크게 SWR과 TanStack Query(구 React Query)가 있다.
우선 도입 전 상황은 이렇다.
- 프론트 스택에 대해 잘 모르고 시간도 없어서 러닝 커브가 크지 않아야 한다.
- 같은 이유로, 풍부한 레퍼런스가 있어야 한다.
- 대부분 단일 앤드 포인트의 단순 조회 로직만 있어서 풍부한 기능이 필요 없다. (조회, 로딩, 에러 처리만 필요)
- Mutation이 거의 없고 Pagination이 필요 없다.
- 데이터가 실시간으로 변하지 않고, 변경 주기가 매우 길다.
SWR이 위의 상황에 가장 적합했다.
- TanStack Query에 비해 더 적은 보일러플레이트
- 단순 기능
- 적은 용량 (5.2kB vs 15.5kB)
- TanStack Query보단 낮지만 절대적으로 높은 주간 다운로드 수 (330만 vs 650만)
SWR 적용
기존에는 각 페이지마다 요청, 에러처리, 로딩처리가 개별로 구현되어 있었다.
'use client'
// ...
export default function Home() {
const [items, setItems] = useState<Item[]>()
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
itemsHandler()
}, [])
const itemsHandler = async () => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/items`)
if (!response.ok) {
throw new Error('서버에서 데이터를 가져오는 데 실패했습니다.')
}
const data = await response.json()
setItems(data.data)
} catch (error) {
setError('데이터를 불러오는 중 오류가 발생했습니다.')
} finally {
setIsLoading(false)
}
}
if(isLoading) {
return <p>로딩 중...</p>
}
if(error) {
return <p>{error}</p>
}
return (
<>
<div>
{items?.map((item) => (<ItemCard ... />))}
</div>
</>
)
}
당연히 회사 코드 아니고 예제로 만들었다.
외부에서 데이터를 가져오는 페이지마다 항상 useState 3개와 데이터 패치 함수를 만들어주어야 했다.
이를 SWR을 적용하면 아래와 같다.
'use client'
// ...
interface ItemResponse {
data: Item[]
message: string
}
export default function Home() {
const { data, error, isLoading } = useSWR<ItemResponse>(`/api/items`)
if(isLoading) {
return <p>로딩 중...</p>
}
if(error) {
return <p>{error}</p>
}
const items = data?.data || []
return (
<>
<div>
{items.map((item) => (<ItemCard ... />))}
</div>
</>
)
}
아까 예제를 보면 fetcher가 있었는데 여기는 없다. 이는 프로바이더에서 공통으로 쓰도록 했기 때문이다.
// SWRProvider.tsx
'use client'
import { SWRConfig } from 'swr'
import { ReactNode } from 'react'
const fetcher = (url: string) => fetch(url).then((res) => res.json())
export default function SWRProvider({ children }: { children: ReactNode }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: false, // 탭 포커스 시 다시 요청 금지
revalidateOnReconnect: false, // 온라인 복귀 시 다시 요청 금지
refreshInterval: 0, // 주기적 요청 금지
}}
>
{children}
</SWRConfig>
)
}
// layout.tsx
export default async function RootLayout({ children, params }: RootLayoutProps) {
return (
<html lang={locale}>
<body>
<SWRProvider>
<Header />
<main>{children}</main>
<Footer />
</SWRProvider>
</body>
</html>
)
}
SWRProvider를 만들어서, 공통으로 쓰일 패처와 설정들을 해주었다.
만약 지정한 패처 말고 다른 패처를 쓰고 싶다면 useSWR에서 커스텀을 넣어주면 된다.
그 외에, SWR은 기본 설정이 탭 포커스가 될 때, 온라인 전환 시, 또 주기적으로 재요청을 보내서 요청이 과도하게 보내게 된다. 그래서 이 설정을 꺼주어서 요청 빈도를 줄였다.
이렇게 총 7페이지에 걸쳐서 순 93줄을 줄일 수 있었다. (늘어난 줄 수 - 줄어든 줄 수)
'TIL' 카테고리의 다른 글
TIL #128 : PNG 이미지를 WebP 확장자로 변환하여 95% 용량 절감 (0) | 2025.04.07 |
---|---|
TIL #127 : Axios Interceptor 도입으로 인증 공통화 및 141줄 절감 (0) | 2025.04.07 |
TIL #125 : 멀티스테이지 & .dockerignore로 Next.js Docker 이미지 용량 절반 줄이기 (1) | 2025.04.02 |
TIL #124 : Spring Data Redis에서 Lua Script 사용하기 (1) | 2024.11.18 |
TIL #123 : 배치 작업에는 꼭 정렬 하기 (0) | 2024.11.17 |
요약
SWR이라는 데이터 패치 라이브러리를 도입하여 외부에서 데이터를 불러올 때 로딩, 에러, 데이터를 단순하게 관리하고, 7페이지에서 총 순 93줄을 줄였다.
상황
인턴으로 간 회사에서 코드를 보니 매 페이지마다 외부 요청 페이지에서 요청, 로딩, 에러를 일일이 useState, fetch로 구현하고 있었다.
해결
SWR 라이브러리를 도입하였다.
SWR란?
SWR은 데이터 패치 리액트 훅이다.
Stale-While-Revalidate이라는 HTTP 캐시 무효 전략을 기반으로 만들어진 라이브러리로, Stale-While-Revalidate은 우선 캐시된 데이터를 반환하면서 데이터를 요청하고, 응답받으면 이를 최신화하는 전략이다.
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then((res) => res.json());
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
공식 문서에 나온 간단한 예제이다.
useSWR을 통해 원하는 url과 패처를 지정해 주면, 그에 대한 응답, 에러, 로딩처리를 해준다.
SWR 채택 이유
데이터 패치 라이브러리로 크게 SWR과 TanStack Query(구 React Query)가 있다.
우선 도입 전 상황은 이렇다.
- 프론트 스택에 대해 잘 모르고 시간도 없어서 러닝 커브가 크지 않아야 한다.
- 같은 이유로, 풍부한 레퍼런스가 있어야 한다.
- 대부분 단일 앤드 포인트의 단순 조회 로직만 있어서 풍부한 기능이 필요 없다. (조회, 로딩, 에러 처리만 필요)
- Mutation이 거의 없고 Pagination이 필요 없다.
- 데이터가 실시간으로 변하지 않고, 변경 주기가 매우 길다.
SWR이 위의 상황에 가장 적합했다.
- TanStack Query에 비해 더 적은 보일러플레이트
- 단순 기능
- 적은 용량 (5.2kB vs 15.5kB)
- TanStack Query보단 낮지만 절대적으로 높은 주간 다운로드 수 (330만 vs 650만)
SWR 적용
기존에는 각 페이지마다 요청, 에러처리, 로딩처리가 개별로 구현되어 있었다.
'use client'
// ...
export default function Home() {
const [items, setItems] = useState<Item[]>()
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
itemsHandler()
}, [])
const itemsHandler = async () => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/items`)
if (!response.ok) {
throw new Error('서버에서 데이터를 가져오는 데 실패했습니다.')
}
const data = await response.json()
setItems(data.data)
} catch (error) {
setError('데이터를 불러오는 중 오류가 발생했습니다.')
} finally {
setIsLoading(false)
}
}
if(isLoading) {
return <p>로딩 중...</p>
}
if(error) {
return <p>{error}</p>
}
return (
<>
<div>
{items?.map((item) => (<ItemCard ... />))}
</div>
</>
)
}
당연히 회사 코드 아니고 예제로 만들었다.
외부에서 데이터를 가져오는 페이지마다 항상 useState 3개와 데이터 패치 함수를 만들어주어야 했다.
이를 SWR을 적용하면 아래와 같다.
'use client'
// ...
interface ItemResponse {
data: Item[]
message: string
}
export default function Home() {
const { data, error, isLoading } = useSWR<ItemResponse>(`/api/items`)
if(isLoading) {
return <p>로딩 중...</p>
}
if(error) {
return <p>{error}</p>
}
const items = data?.data || []
return (
<>
<div>
{items.map((item) => (<ItemCard ... />))}
</div>
</>
)
}
아까 예제를 보면 fetcher가 있었는데 여기는 없다. 이는 프로바이더에서 공통으로 쓰도록 했기 때문이다.
// SWRProvider.tsx
'use client'
import { SWRConfig } from 'swr'
import { ReactNode } from 'react'
const fetcher = (url: string) => fetch(url).then((res) => res.json())
export default function SWRProvider({ children }: { children: ReactNode }) {
return (
<SWRConfig
value={{
fetcher,
revalidateOnFocus: false, // 탭 포커스 시 다시 요청 금지
revalidateOnReconnect: false, // 온라인 복귀 시 다시 요청 금지
refreshInterval: 0, // 주기적 요청 금지
}}
>
{children}
</SWRConfig>
)
}
// layout.tsx
export default async function RootLayout({ children, params }: RootLayoutProps) {
return (
<html lang={locale}>
<body>
<SWRProvider>
<Header />
<main>{children}</main>
<Footer />
</SWRProvider>
</body>
</html>
)
}
SWRProvider를 만들어서, 공통으로 쓰일 패처와 설정들을 해주었다.
만약 지정한 패처 말고 다른 패처를 쓰고 싶다면 useSWR에서 커스텀을 넣어주면 된다.
그 외에, SWR은 기본 설정이 탭 포커스가 될 때, 온라인 전환 시, 또 주기적으로 재요청을 보내서 요청이 과도하게 보내게 된다. 그래서 이 설정을 꺼주어서 요청 빈도를 줄였다.
이렇게 총 7페이지에 걸쳐서 순 93줄을 줄일 수 있었다. (늘어난 줄 수 - 줄어든 줄 수)
'TIL' 카테고리의 다른 글
TIL #128 : PNG 이미지를 WebP 확장자로 변환하여 95% 용량 절감 (0) | 2025.04.07 |
---|---|
TIL #127 : Axios Interceptor 도입으로 인증 공통화 및 141줄 절감 (0) | 2025.04.07 |
TIL #125 : 멀티스테이지 & .dockerignore로 Next.js Docker 이미지 용량 절반 줄이기 (1) | 2025.04.02 |
TIL #124 : Spring Data Redis에서 Lua Script 사용하기 (1) | 2024.11.18 |
TIL #123 : 배치 작업에는 꼭 정렬 하기 (0) | 2024.11.17 |