React Suspense๋ฅผ ํ์ฉํ์ฌ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ๋ก๋ฉ ๋ฐ ์บ์ฑ ์ ๋ต์ ๊ตฌํํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ํฌ๊ด์ ์ธ ๊ฐ์ด๋์ ๋๋ค.
React Suspense ์บ์ ์ ๋ต: ๋ฐ์ดํฐ ๋ก๋ฉ ์บ์ ๊ด๋ฆฌ ๋ง์คํฐํ๊ธฐ
React์ ๋์์ฑ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ผ๋ถ๋ก ๋์ ๋ React Suspense๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ก๋ฉ ์ํ๋ฅผ ์ฒ๋ฆฌํ๋ ์ ์ธ์ ์ธ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ๊ฐ๋ ฅํ ์บ์ฑ ์ ๋ต๊ณผ ๊ฒฐํฉํ๋ฉด Suspense๋ ๋ถํ์ํ ๋คํธ์ํฌ ์์ฒญ์ ๋ฐฉ์งํ๊ณ ์ด์ ์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ์ ์ฆ์ ์ก์ธ์คํ ์ ์๋๋ก ํ์ฌ ์ธ์ง๋๋ ์ฑ๋ฅ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ต๋๋ค. ์ด ๊ฐ์ด๋๋ React Suspense๋ฅผ ์ฌ์ฉํ์ฌ ํจ๊ณผ์ ์ธ ๋ฐ์ดํฐ ๋ก๋ฉ ๋ฐ ์บ์ ๊ด๋ฆฌ ๊ธฐ์ ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃน๋๋ค.
React Suspense ์ดํดํ๊ธฐ
๋ณธ์ง์ ์ผ๋ก React Suspense๋ ๋ฐ์ดํฐ ๋ก๋๋ฅผ ๊ธฐ๋ค๋ฆฌ๋๋ผ ์ฆ์ ๋ ๋๋ง๋ ์ค๋น๊ฐ ๋์ง ์์ ์ ์๋, ์ฆ ๋ณด๋ฅ(suspend)๋ ์ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ผ๋ถ๋ฅผ ๊ฐ์ธ๋ ์ปดํฌ๋ํธ์ ๋๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ณด๋ฅ๋ ๋ Suspense๋ ๋ฐ์ดํฐ๊ฐ ์ฌ์ฉ ๊ฐ๋ฅํด์ง ๋๊น์ง ๋์ฒด UI(์: ๋ก๋ฉ ์คํผ๋)๋ฅผ ํ์ํฉ๋๋ค. ๋ฐ์ดํฐ๊ฐ ์ค๋น๋๋ฉด Suspense๋ ๋์ฒด UI๋ฅผ ์ค์ ์ปดํฌ๋ํธ๋ก ๋ฐ๊ฟ๋๋ค.
React Suspense ์ฌ์ฉ์ ์ฃผ์ ์ด์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ ์ธ์ ๋ก๋ฉ ์ํ: ๋ถ๋ฆฌ์ธ ํ๋๊ทธ๋ ๋ณต์กํ ์ํ ๋ก์ง์ ๊ด๋ฆฌํ ํ์ ์์ด ์ปดํฌ๋ํธ ํธ๋ฆฌ์ ๋ก๋ฉ ์ํ๋ฅผ ์ง์ ์ ์ํ ์ ์์ต๋๋ค.
- ํฅ์๋ ์ฌ์ฉ์ ๊ฒฝํ: ๋ฐ์ดํฐ ๋ก๋ฉ ์ค ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ฌ ์ธ์ง๋๋ ์ง์ฐ ์๊ฐ์ ์ค์ ๋๋ค.
- ์ฝ๋ ๋ถํ : ์ปดํฌ๋ํธ์ ์ฝ๋ ๋ฒ๋ค์ ์ฝ๊ฒ ์ง์ฐ ๋ก๋ํ์ฌ ์ด๊ธฐ ๋ก๋ ์๊ฐ์ ๋์ฑ ํฅ์์ํต๋๋ค.
- ๋์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ: ๋ฉ์ธ ์ค๋ ๋๋ฅผ ์ฐจ๋จํ์ง ์๊ณ ๋ฐ์ดํฐ๋ฅผ ๋์์ ์ผ๋ก ๊ฐ์ ธ์ ๋ฐ์์ฑ ์๋ UI๋ฅผ ๋ณด์ฅํฉ๋๋ค.
๋ฐ์ดํฐ ์บ์ฑ์ ํ์์ฑ
Suspense๊ฐ ๋ก๋ฉ ์ํ๋ฅผ ์ฒ๋ฆฌํ๋ ๋์์๋, ๋ฐ์ดํฐ ์บ์ฑ์ ๋ณธ์ง์ ์ผ๋ก ๊ด๋ฆฌํ์ง๋ ์์ต๋๋ค. ์บ์ฑ์ด ์์ผ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด์ ์ ๋ฐฉ๋ฌธํ๋ ์น์ ์ผ๋ก ์ฌ๋ ๋๋งํ๊ฑฐ๋ ํ์ํ ๋๋ง๋ค ์๋ก์ด ๋คํธ์ํฌ ์์ฒญ์ด ๋ฐ์ํ ์ ์์ผ๋ฉฐ, ์ด๋ ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํฉ๋๋ค:
- ์ฆ๊ฐ๋ ์ง์ฐ ์๊ฐ: ์ฌ์ฉ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๊ฐ์ ธ์ค๋ ๋์ ์ง์ฐ์ ๊ฒฝํํฉ๋๋ค.
- ๋์ ์๋ฒ ๋ถํ: ๋ถํ์ํ ์์ฒญ์ ์๋ฒ ๋ฆฌ์์ค์ ๋ถ๋ด์ ์ฃผ๊ณ ๋น์ฉ์ ์ฆ๊ฐ์ํต๋๋ค.
- ๋์ ์ฌ์ฉ์ ๊ฒฝํ: ์ฆ์ ๋ก๋ฉ ์ํ๋ ์ฌ์ฉ์ ํ๋ฆ์ ๋ฐฉํดํ๊ณ ์ ๋ฐ์ ์ธ ๊ฒฝํ์ ์ ํ์ํต๋๋ค.
๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต์ ๊ตฌํํ๋ ๊ฒ์ React Suspense ์ ํ๋ฆฌ์ผ์ด์ ์ ์ต์ ํํ๋ ๋ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ ์ค๊ณ๋ ์บ์๋ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ํ์ ์์ฒญ ์ ๋ฉ๋ชจ๋ฆฌ์์ ์ง์ ์ ๊ณตํ์ฌ ์ค๋ณต๋ ๋คํธ์ํฌ ํธ์ถ์ ํ์์ฑ์ ์์จ ์ ์์ต๋๋ค.
React Suspense๋ฅผ ์ด์ฉํ ๊ธฐ๋ณธ์ ์ธ ์บ์ ๊ตฌํ
React Suspense์ ํตํฉ๋๋ ๊ฐ๋จํ ์บ์ฑ ๋ฉ์ปค๋์ฆ์ ๋ง๋ค์ด ๋ด ์๋ค. JavaScript Map์ ์ฌ์ฉํ์ฌ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ , ์ฌ์ฉ์ ์ ์ `wrapPromise` ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์ฒ๋ฆฌํ ๊ฒ์ ๋๋ค.
1. `wrapPromise` ํจ์
์ด ํจ์๋ ํ๋ผ๋ฏธ์ค(๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ์์ ์ ๊ฒฐ๊ณผ)๋ฅผ ๋ฐ์ `read()` ๋ฉ์๋๋ฅผ ํฌํจํ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. `read()` ๋ฉ์๋๋ ํด๊ฒฐ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๊ฑฐ๋, ํ๋ผ๋ฏธ์ค๊ฐ ์ฌ์ ํ ๋๊ธฐ ์ค์ด๋ฉด ํด๋น ํ๋ผ๋ฏธ์ค๋ฅผ ๋์ง๊ฑฐ๋, ํ๋ผ๋ฏธ์ค๊ฐ ๊ฑฐ๋ถ๋๋ฉด ์ค๋ฅ๋ฅผ ๋์ง๋๋ค. ์ด๊ฒ์ด Suspense๊ฐ ๋น๋๊ธฐ ๋ฐ์ดํฐ์ ํจ๊ป ์๋ํ ์ ์๋๋ก ํ๋ ํต์ฌ ๋ฉ์ปค๋์ฆ์ ๋๋ค.
function wrapPromise(promise) {
let status = 'pending';
let result;
let suspender = promise.then(
r => {
status = 'success';
result = r;
},
e => {
status = 'error';
result = e;
}
);
return {
read() {
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
} else if (status === 'success') {
return result;
}
},
};
}
2. ์บ์ ๊ฐ์ฒด
์ด ๊ฐ์ฒด๋ JavaScript Map์ ์ฌ์ฉํ์ฌ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค. ๋ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ (์บ์์ ์๋ ๊ฒฝ์ฐ) `wrapPromise` ํจ์๋ก ๋ํํ๋ `load` ํจ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
function createCache() {
let cache = new Map();
return {
load(key, promise) {
if (!cache.has(key)) {
cache.set(key, wrapPromise(promise()));
}
return cache.get(key);
},
};
}
3. React ์ปดํฌ๋ํธ์ ํตํฉํ๊ธฐ
์ด์ React ์ปดํฌ๋ํธ์์ ์บ์๋ฅผ ์ฌ์ฉํด ๋ด ์๋ค. `load` ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ `Profile` ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ๊ฒ์ ๋๋ค.
import React, { Suspense, useRef } from 'react';
const dataCache = createCache();
function fetchUserData(userId) {
return fetch(`https://api.example.com/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
});
}
function ProfileDetails({ userId }) {
const userData = dataCache.load(userId, () => fetchUserData(userId));
const user = userData.read();
return (
{user.name}
Email: {user.email}
Location: {user.location}
);
}
function Profile({ userId }) {
return (
Loading profile... ์ด ์์์์:
- `createCache()`๋ฅผ ์ฌ์ฉํ์ฌ `dataCache` ์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค.
- `ProfileDetails` ์ปดํฌ๋ํธ๋ `dataCache.load()`๋ฅผ ํธ์ถํ์ฌ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- `dataCache.load()` ๊ฒฐ๊ณผ์ ๋ํด `read()` ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค. ๋ฐ์ดํฐ๊ฐ ์์ง ์ฌ์ฉ ๊ฐ๋ฅํ์ง ์์ผ๋ฉด Suspense๋ ๋์ ธ์ง ํ๋ผ๋ฏธ์ค๋ฅผ catchํ๊ณ `Profile` ์ปดํฌ๋ํธ์ ์ ์๋ ๋์ฒด UI๋ฅผ ํ์ํฉ๋๋ค.
- `Profile` ์ปดํฌ๋ํธ๋ `ProfileDetails`๋ฅผ `Suspense` ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ์ ๋ฐ์ดํฐ ๋ก๋ฉ ์ค์ ๋์ฒด UI๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ค์ ๊ณ ๋ ค ์ฌํญ:
- `https://api.example.com/users/${userId}`๋ฅผ ์ค์ API ์๋ํฌ์ธํธ๋ก ๋ฐ๊พธ์ญ์์ค.
- ์ด๊ฒ์ ๋งค์ฐ ๊ธฐ๋ณธ์ ์ธ ์์์ ๋๋ค. ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ค๋ฅ ์ํ์ ์บ์ ๋ฌดํจํ๋ฅผ ๋ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
๊ณ ๊ธ ์บ์ฑ ์ ๋ต
์์์ ๊ตฌํํ ๊ธฐ๋ณธ ์บ์ฑ ๋ฉ์ปค๋์ฆ์ ์ข์ ์์์ ์ด์ง๋ง, ํ๊ณ๊ฐ ์์ต๋๋ค. ๋ ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ, ๋ ๊ณ ๊ธ ์บ์ฑ ์ ๋ต์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
1. ์๊ฐ ๊ธฐ๋ฐ ๋ง๋ฃ
๋ฐ์ดํฐ๋ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ์ค๋๋ ์ ์์ต๋๋ค. ์๊ฐ ๊ธฐ๋ฐ ๋ง๋ฃ ์ ์ฑ ์ ๊ตฌํํ๋ฉด ์บ์๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ก ๊ณ ์ณ์ง๋๋ค. ๊ฐ ์บ์๋ ํญ๋ชฉ์ ํ์์คํฌํ๋ฅผ ์ถ๊ฐํ๊ณ , ํน์ ์๊ณ๊ฐ๋ณด๋ค ์ค๋๋ ๊ฒฝ์ฐ ์บ์ ํญ๋ชฉ์ ๋ฌดํจํํ ์ ์์ต๋๋ค.
function createCacheWithExpiration(expirationTime) {
let cache = new Map();
return {
load(key, promise) {
if (cache.has(key)) {
const { data, timestamp } = cache.get(key);
if (Date.now() - timestamp < expirationTime) {
return data;
}
cache.delete(key);
}
const wrappedPromise = wrapPromise(promise());
cache.set(key, { data: wrappedPromise, timestamp: Date.now() });
return wrappedPromise;
},
};
}
์ฌ์ฉ ์์:
const dataCache = createCacheWithExpiration(60000); // Cache expires after 60 seconds
2. ์บ์ ๋ฌดํจํ
๋๋ก๋ ์๋ฒ์์ ๋ฐ์ดํฐ๊ฐ ์ ๋ฐ์ดํธ๋ ๋์ ๊ฐ์ด ์บ์๋ฅผ ์๋์ผ๋ก ๋ฌดํจํํด์ผ ํ ์๋ ์์ต๋๋ค. ์บ์ ๊ฐ์ฒด์ `invalidate` ๋ฉ์๋๋ฅผ ์ถ๊ฐํ์ฌ ํน์ ํญ๋ชฉ์ ์ ๊ฑฐํ ์ ์์ต๋๋ค.
function createCacheWithInvalidation() {
let cache = new Map();
return {
load(key, promise) {
// ... (existing load function)
},
invalidate(key) {
cache.delete(key);
},
};
}
์ฌ์ฉ ์์:
const dataCache = createCacheWithInvalidation();
// ...
// When data is updated on the server:
dataCache.invalidate(userId);
3. LRU (๊ฐ์ฅ ์ต๊ทผ์ ์ฌ์ฉ๋์ง ์์) ์บ์
LRU ์บ์๋ ์บ์๊ฐ ์ต๋ ์ฉ๋์ ๋๋ฌํ๋ฉด ๊ฐ์ฅ ์ต๊ทผ์ ์ฌ์ฉ๋์ง ์์ ํญ๋ชฉ์ ์ ๊ฑฐํฉ๋๋ค. ์ด๋ ๊ฐ์ฅ ์์ฃผ ์ก์ธ์ค๋๋ ๋ฐ์ดํฐ๊ฐ ์บ์์ ๋จ์ ์๋๋ก ๋ณด์ฅํฉ๋๋ค.
LRU ์บ์๋ฅผ ๊ตฌํํ๋ ค๋ฉด ๋ ๋ณต์กํ ๋ฐ์ดํฐ ๊ตฌ์กฐ๊ฐ ํ์ํ์ง๋ง, `lru-cache`์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด ๊ณผ์ ์ ๋จ์ํํ ์ ์์ต๋๋ค.
const LRU = require('lru-cache');
function createLRUCache(maxSize) {
const cache = new LRU({ max: maxSize });
return {
load(key, promise) {
if (cache.has(key)) {
return cache.get(key);
}
const wrappedPromise = wrapPromise(promise());
cache.set(key, wrappedPromise);
return wrappedPromise;
},
};
}
4. ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉํ๊ธฐ
์ฌ๋ฌ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ React Suspense๋ฅผ ์ฌ์ฉํ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ๋ฐ ์บ์ฑ์ ๋จ์ํํ ์ ์์ต๋๋ค. ์ธ๊ธฐ ์๋ ์ต์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- React Query: React ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋ฒ ์ํ๋ฅผ ๊ฐ์ ธ์ค๊ณ , ์บ์ํ๊ณ , ๋๊ธฐํํ๊ณ , ์ ๋ฐ์ดํธํ๋ ๊ฐ๋ ฅํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
- SWR: React Hooks๋ฅผ ์ฌ์ฉํ ์๊ฒฉ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์ํ ๊ฒฝ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
- Relay: GraphQL API์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ ์ธ์ ์ด๊ณ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ React์ฉ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ํ๋ ์์ํฌ์ ๋๋ค.
์ด๋ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ข ์ข ๋ด์ฅ๋ ์บ์ฑ ๋ฉ์ปค๋์ฆ, ์๋ ์บ์ ๋ฌดํจํ ๋ฐ ๊ธฐํ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ์์ฑํด์ผ ํ๋ ์์ฉ๊ตฌ ์ฝ๋์ ์์ ํฌ๊ฒ ์ค์ผ ์ ์์ต๋๋ค.
React Suspense๋ฅผ ์ฌ์ฉํ ์ค๋ฅ ์ฒ๋ฆฌ
React Suspense๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ์ค์ ๋ฐ์ํ๋ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฉ์ปค๋์ฆ๋ ์ ๊ณตํฉ๋๋ค. ์๋ฌ ๋ฐ์ด๋๋ฆฌ(Error Boundaries)๋ฅผ ์ฌ์ฉํ์ฌ ๋ณด๋ฅ๋๋ ์ปดํฌ๋ํธ๊ฐ ๋์ง๋ ์ค๋ฅ๋ฅผ ์ก์ ์ ์์ต๋๋ค.
import React, { Suspense } from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return Something went wrong.
;
}
return this.props.children;
}
}
function App() {
return (
Loading...