diff --git a/src/apis/gifAPIService.ts b/src/apis/gifAPIService.ts index 6653d3b9..8b8fec3d 100644 --- a/src/apis/gifAPIService.ts +++ b/src/apis/gifAPIService.ts @@ -2,7 +2,7 @@ import { GifsResult } from '@giphy/js-fetch-api'; import { IGif } from '@giphy/js-types'; import { GifImageModel } from '../models/image/gifImage'; -import { apiClient, ApiError } from '../utils/apiClient'; +import { apiClient, apiClientWithCache, ApiError } from '../utils/apiClient'; const API_KEY = process.env.GIPHY_API_KEY; if (!API_KEY) { @@ -43,7 +43,7 @@ export const gifAPIService = { * @returns {Promise} * @ref https://developers.giphy.com/docs/api/endpoint#!/gifs/trending */ - getTrending: async (): Promise => { + getTrending(): Promise { const url = apiClient.appendSearchParams(new URL(`${BASE_URL}/trending`), { api_key: API_KEY, limit: `${DEFAULT_FETCH_COUNT}`, @@ -52,6 +52,14 @@ export const gifAPIService = { return fetchGifs(url); }, + + getTrendingWithCache: async () => { + return await apiClientWithCache({ + queryKey: 'trending-gifs', + staleTime: 1000 * 60 * 5, + queryFn: gifAPIService.getTrending + }); + }, /** * 검색어에 맞는 gif 목록을 가져옵니다. * @param {string} keyword diff --git a/src/pages/Search/hooks/useGifSearch.tsx b/src/pages/Search/hooks/useGifSearch.tsx index 8634d9e0..3d27ffbd 100644 --- a/src/pages/Search/hooks/useGifSearch.tsx +++ b/src/pages/Search/hooks/useGifSearch.tsx @@ -74,7 +74,7 @@ const useGifSearch = () => { if (status !== SEARCH_STATUS.BEFORE_SEARCH) return; try { - const gifs = await gifAPIService.getTrending(); + const gifs = await gifAPIService.getTrendingWithCache(); setGifList(gifs); } catch (error) { handleError(error); diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index f91a779d..8f1f30d2 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -1,3 +1,5 @@ +import cache from './cache'; + export class ApiError extends Error { constructor(public status: number, message?: string) { super(message); @@ -22,3 +24,24 @@ export const apiClient = { return newUrl; } }; + +interface ApiClientWithCacheArgs { + queryFn: () => Promise; + queryKey: string; + staleTime: number; +} + +export const apiClientWithCache = async ({ + queryFn, + queryKey, + staleTime +}: ApiClientWithCacheArgs) => { + const cachedData = cache.get(queryKey); + + if (cachedData && cache.isValidCache(queryKey)) return cachedData; + + const newData = await queryFn(); + cache.set(queryKey, newData, staleTime); + + return newData; +}; diff --git a/src/utils/cache.ts b/src/utils/cache.ts new file mode 100644 index 00000000..31ea7d77 --- /dev/null +++ b/src/utils/cache.ts @@ -0,0 +1,30 @@ +interface CacheItem { + data: T; + expiredTime: number; +} + +class Cache { + private cache: Record> = {}; + + isValidCache(key: string): boolean { + const item = this.cache[key]; + if (!item) return false; + + return Date.now() <= item.expiredTime; + } + + get(key: string): T | null { + if (!this.cache[key]) return null; + + return this.cache[key].data; + } + + set(key: string, data: T, ttl: number): void { + const expiredTime = Date.now() + ttl; + this.cache[key] = { data, expiredTime }; + } +} + +const cache = new Cache(); + +export default cache;