์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์
๊ผญ ํด๋ณด๊ณ ์ถ์๋ ๊ฒ ์ค ํ๋๋ ๋ชฉ์ ์ด์๋ค.
๊ณผ์ฐ ๋ชฉ์ ์ด ๋ฌด์์ด๋ฉฐ,
React๋ ์ด๋ค์์ผ๋ก ๋ชฉ์ ์ ํ ๊น?
https://lotto-magic-frontend.vercel.app/
๋ก๋ ๋ฒํธ ์์ฑ ๋ง๋ฒ์ง
ํ์ด ์์ 3๊ฐ๋ฅผ ์ ํํ๋ฉด ์ค๋์ ๋ก๋ ๋ฒํธ์ ํ์ด ์ ์๋ฅผ ๋ง๋ค์ด์ฃผ๋ ์ฌ์ดํธ์ ๋๋ค.
lotto-magic-frontend.vercel.app
1. ํ๊ฒฝ๊ตฌ์ฑ
- React
- Next.js
- WebStorm
- TypeScript
2. ๋ชฉ์ (Mocking)
- ๊ฐ๋ฐ์์์ ๋ชฉ์ ์ ์ค์ ์ ํ์ด๋ API๊ฐ ์์ง ์ค๋น๋์ง ์์์ ๋, ๊ทธ ์ญํ ์ ๋์ ํ ๊ฐ์ง ๋ฐ์ดํฐ๋ ํ๊ฒฝ์ ๋ง๋๋ ๊ฒ์ ์๋ฏธํ๋ค.
- ํ๋ก ํธ์๋ ๊ฐ๋ฐ ์ ๋ฐฑ์๋ API ์๋ฒ๊ฐ ์์ฑ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ , ๋ฏธ๋ฆฌ ์ ์๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ด์ฉํด UI์ ๋น์ฆ๋์ค ๋ก์ง์ ๊ตฌํํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
- ๋ค์ํ ์ํฉ ํ ์คํธ๊ฐ ๊ฐ๋ฅํ๋ค. ์๋ฒ ์๋ฌ ๋ฑ ์ค์ ์๋ฒ์์ ์ฌํํ๊ธฐ ๋ฒ๊ฑฐ๋ก์ด ์ํฉ์ ์ฝ๊ฒ ํ ์คํธํ ์ ์๋ค.
- ์ธ๋ถ API ์ํ์ ์๊ด์์ด ํ๋ก ํธ์๋ ๋ก์ง์ ์ ํ์ฑ๋ง ๊ฒ์ฆํ ์ ์๋ค. ์ฆ, ๋ ๋ฆฝ์ ์ธ ํ ์คํธ๊ฐ ๊ฐ๋ฅํ๋ค.
3. MSW(Mock Service Worker)
- MSW๋ ๋ธ๋ผ์ฐ์ ์ Service Worker API๋ฅผ ์ฌ์ฉํด์ ๋คํธ์ํฌ ์์ค์์ API ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ , ๊ฐ์ง ์๋ต์ ๋ณด๋ด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
- ์ค์ ๋คํธ์ํฌ ์์ฒญ์ด ๋๊ฐ๋ ๊ฒฝ๋ก๋ฅผ ์ค๊ฐ์์ ๊ฐ๋ก์ฑ๊ธฐ ๋๋ฌธ์ ํจ์ฌ ํ์ค๊ฐ ์๋ ๋ชจํน์ด ๊ฐ๋ฅํ๋ค.
- ํฌ๋กฌ ๊ฐ๋ฐ์ ๋๊ตฌ์ Network ํญ์์ ์์ฒญ์ด ์ค์ ๋ก ๋๊ฐ๋ ๊ฒ์ฒ๋ผ ๊ธฐ๋ก์ด ๋จ๋๋ค. ๋๋ฒ๊น ์ ๋งค์ฐ ํฐ ์ฅ์ ์ด ๋๋ค.
- MSW์ ๋์ ๊ณผ์
a) ์์ฒญ๋ฐ์ : ๋ฆฌ์กํธ ์ฑ์์ fetch๋ axios๋ฅผ ํตํด API ์์ฒญ์ ๋ณด๋ธ๋ค.
b) ์ธํฐ์ ํธ : ๋ธ๋ผ์ฐ์ ์ ๋ฑ๋ก๋ Service Worker๊ฐ ์ด ์์ฒญ์ ๊ฐ๋ก์ฑ๋ค.
c) ํธ๋ค๋ฌํ์ธ : MSW์ ๋ฏธ๋ฆฌ ์ ์ํด๋ ํธ๋ค๋ฌ ์ค์ ํด๋น ์์ฒญ ์ฃผ์์ ์ผ์นํ๋ ๊ฒ์ด ์๋์ง ํ์ธํ๋ค.
d) ์๋ต๋ณํ : ์ผ์นํ๋ ํธ๋ค๋ฌ๊ฐ ์๋ค๋ฉด ๊ฐ์ง ์๋ต(JSON)์ ๋ธ๋ผ์ฐ์ ์ ์ ๋ฌํ๋ค. ๋ง์ฝ ์๋ค๋ฉด ์ค์ ๋คํธ์ํฌ๋ก ์์ฒญ์ ํต๊ณผ์ํจ๋ค.
4. MSW
- ์ค์นํ๊ธฐ
ํ๋ก์ ํธ ๋ด์ ์๋ ํฐ๋ฏธ๋์ ๋ค์ด๊ฐ์ ์๋ ์ฝ๋๋ฅผ ์ ์ด ์ค์นํ๋ค. ์ฌ๊ธฐ์ --save-dev๋ ๊ฐ๋ฐ๊ณผ ํ ์คํธ ์์๋ง ํ์ํ ๋๊ตฌ๋ก ์ฉ๋๋ฅผ ์ ์ ๊ฒ์ด๊ธฐ์ ์ต์ข ๊ฒฐ๊ณผ๋ฌผ์์๋ ์ ์ธ๋๋ค.
npm install msw --save-dev

- ๋ธ๋ผ์ฐ์ ์ ์๋น์ค ์์ปค ๋ฑ๋กํ๊ธฐ
๋ธ๋ผ์ฐ์ ๊ฐ ๋ฐ์ผ๋ก ๋ด๋ณด๋ด๋ ๋คํธ์ํฌ ์์ฒญ์ ๊ฐ๋ก์ฑ ์ ์๋ ๊ถํ์ ์ป๊ธฐ ์ํด ์๋น์ค ์์ปค๋ฅผ ๋ฑ๋กํ๋ค. ์ด๋ ๋ฑ๋ก์ ์๋ฃํ๋ฉด public ๋๋ ํ ๋ฆฌ์ MockServiceWorker๋ผ๋ ํ์ผ์ด ์๊ธด๋ค.
npx msw init public --save


- browser.ts ์์ฑํ๊ธฐ
mocks/browser.ts : mocks ๋๋ ํ ๋ฆฌ๋ฅผ ๋ง๋ค๊ณ ๊ทธ ์์ browser.ts ํ์ผ์ ์์ฑํ๋ค. handlers๋ผ๋ ํ์ผ์ ๋ฐ๋ก ๋ค์์ ๋ง๋ค๊ฑฐ๋ค. ์ด handlersํ์ผ์๋ ์ด๋ค ์ฃผ์์ ์์ฒญ์ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ์ง ๊ท์น๋ค์ ๋ชจ์๋๋๋ฐ, browser.ts๊ฐ ๊ฐ์ ธ์จ ๊ท์น๋ค(hanlders)์ ์๋น์ค ์์ปค์ ์ฃผ์ ํ์ฌ ์ค์ ๊ฐ์ง ์๋ฒ ์ธ์คํด์ค(worker)๋ฅผ ๋ง๋ ๋ค. handlers ๋ฐฐ์ด์ ๋ด๊ธด ์ฌ๋ฌ ๊ท์น์ ๊บผ๋ด์ผํ๋, ...(์ ๊ฐ์ฐ์ฐ์)๋ฅผ ์ฌ์ฉํ๋ค.

import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
import { setupWorker } from 'msw/browser';๋ ๋ธ๋ฌ์ฐ์ ์ฉ ์๋น์ค ์์ปค๋ฅผ ์์ฑํ๋ ํต์ฌ ํจ์๋ฅผ ๊ฐ์ ธ์จ๋ค.
import { handlers } from './handlers';๋ ์ด๋ค ์์ฒญ์ ๋ณด๋ผ์ง ๊ท์น์ ๋ชจ์๋ ํ์ผ์ธ handlers ํ์ผ์ ๊ฐ์ ธ์จ๋ค.
export const worker = setupWorker(...handlers);๋ ๊ฐ์ ธ์จ hanlders๋ฅผ ์๋น์ค ์์ปค์ ์ฃผ์ ํ์ฌ ์ค์ ๊ฐ์ง ์๋ฒ ์ธ์คํด์ค(worker)๋ฅผ ๋ง๋ ๋ค. ...(์ ๊ฐ์ฐ์ฐ์)๋ฅผ ์ฌ์ฉํด์ handlers ๋ฐฐ์ด์ ๋ด๊ธด ์ฌ๋ฌ ๊ท์น์ ํ๋์ฉ ๊บผ๋ด์ ์ค์ ํจ์์ ์ ๋ฌํ๋ค.
- handler.ts ์์ฑํ๊ธฐ
์ด๊ฑด ๊ฐ์ ์ฝ๋์ ๋ง์ถฐ์ ์์ฑํด์ผํ๋ค. ์ด ์ฝ๋๋ฅผ ๋ ์ ์ดํดํ๊ณ ์ถ์ผ๋ฉด [ํ์ด์ ๋ก๋ ๋ง๋ฒ์ง] ์นดํ ๊ณ ๋ฆฌ๋ก ๋ค์ด๊ฐ์ "๊ตฌ์ฑ"์ ์ฝ์ด๋ณด๋ฉด ๋์์ด ๋ ๊ฒ์ด๋ค.
a) ์ผ๋จ ์ด ํ๋ก์ ํธ์ ๋ฉ์ธํ๋ฉด์๋ ์ฌ์ฉ์๊ฐ ๋ง๋ฒ์ง์ ๋ฃ์ ์์๋ค์ 3๊ฐ์ง ๊ณจ๋ผ์ผํ๋ค. ๊ทธ๋์ ๊ทธ ์์๋ฅผ ๋ณด์ฌ์ค ๋ถ๋ถ์ ํธ๋ค๋ฌ์ ์์ฑํด์ผํ๋ค. (์ ํ ๋ชฉ๋ก ์์ฑ : GET)
b) ์ฌ์ฉ์๊ฐ 3๊ฐ์ง ์ ํ ์์๋ฅผ ๊ณ ๋ฅด๋ฉด ๋ก๋ ์ถ์ ์ ์คํํด์ผํ๋ค. ์ ํํ ์ต์ ์ด 3๊ฐ์ง์ธ์ง ํ์ธํ๊ณ , ์๋๋ฉด ์๋ฌ๋ฅผ ๋ฐํํ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ฒฐ๊ณผ๋ฌผ์ ๋ฐํํ๋ค. (๋ก๋ ๋ง๋ฒ์ง ์์ฑ : POST)
import { http, HttpResponse } from 'msw';
// ์ ํ์ต์
๋ค
const optionItems = [
'ํ์ด',
'์กฐ์๋์๋์',
'์ ์์์๋ฆฌ',
'๊ฐ์ฉ๋๊ฟ',
'๋์์ง๊ฐ',
'์ํ์์ ๊ฒฌ์ง๋ช
',
'ํ์น์์ด๋ณด์ด๋๋ด์ธ์',
'๋ด๋',
'๋ค์ด์๋ชฌ๋๊ด์ฐ์ฃผ์ธ',
'๊ฐ๊ฟ',
'๋ด์ธ์์์ง์์นํฉ๊ธํฐ์ผ',
'์์ ๋๋์์ฃ ',
'๋ด์ง๋ง๋ จ',
'์ธ๊ณ์ธ์ํ
๋ ํ์',
'ํด์ฌ๊ฐ',
'1๋ฑ์ดํ์ํด',
];
// ์์ฒญ ๋ฐ๋ ํ์
// POST ์์ฒญ ์ ํ๋ก ํธ์๋์์ ๋ณด๋ด์ค ๋ฐ์ดํฐ์ ๊ท๊ฒฉ์ TypeScript๋ก ์ ์ํ์
type DrawRequestBody = {
selectedOptions: string[];
};
export const handlers = [
// ํ๋ก ํธ์๋๊ฐ ํ๋ฉด์ ๊ทธ๋ฆด ๋ ํ์ํ ์ ํํ ์์๋ค์ ๋ฐํํ๋ค.
// ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ด๋ ค์ฃผ๋ ์ ์ ์๋ต์ด๋๊น GET์ผ๋ก...!
http.get('/api/lotto/options', () => {
return HttpResponse.json({
options: optionItems,
});
}),
// ๋ก๋ ๋ฒํธ ์ถ์ฒจ์ ์คํํ๋ค.
http.post('/api/lotto/draw', async ({ request }) => {
// request.json์ ํตํด ํ๋ก ํธ์๋์์ ๋ณด๋ธ JSON ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์จ๋ค.
const body = (await request.json()) as Partial<DrawRequestBody>;
const selectedOptions = body.selectedOptions ?? [];
// ์์ 3๊ฐ์ง๋ฅผ ์ ํํ์ง ์์์ ๊ฒฝ์ฐ 400 Bad Request ์๋ฌ๋ฅผ ๋ฐํํ๋ค.
if (selectedOptions.length !== 3) {
return HttpResponse.json(
{
message: '์์ 3๊ฐ์ง๋ฅผ ์ ํํด์ฃผ์ธ์.',
},
{ status: 400 }
);
}
// 3๊ฐ๋ฅผ ์ ๋๋ก ์ ํํ๋ค๋ฉด ๋ก๋ ๋ฒํธ์ ํ์ด์ ์ ์, ์ด๋ฏธ์ง ๊ฒฝ๋ก ๋ฑ์ ๋ฐํํ๋ค.
// ์๋๋ ํ์ด์ ์ ์, ๋ฉ์์ง๋ ๊ณ์ฐ ๋ก์ง์ ์ํด ๋ํ๋์ผํ๋๋ฐ,
// ์ง๊ธ์ ํ
์คํธ ๊ณผ์ ์ด๋๊น ์ด๋ค ๊ฑธ ์ ํํ๋ ์๋ ์ค์ ํ ๊ฒฐ๊ณผ๊ฐ ๋ํ๋๋ค.
return HttpResponse.json({
numbers: [3, 11, 19, 27, 34, 42],
luckScore: 84,
luckMessage: '์ฐ์ฃผ ํต์ ์ฐ๊ฒฐ ์๋ฃ',
selectedOptions,
spellNumber: 3,
spellImageUrl: '/images/spells/3.png',
});
}),
];
5. ํ๋ก ํธ ์ฝ๋ ์ดํด๋ณด๊ธฐ - MSW๋ ์ฐ๊ฒฐํด์
- HomePage.tsx(๋ฉ์ธํ์ด์ง)_ ์์ฒญ ๋ณด๋ด๊ธฐ

<button
type="button"
className={styles.createButton}
onClick={handleCreateClick}
disabled={isSubmitting}
>
์ฟต์ง์ฟต์ง ๋ง๋ค๊ธฐ ๋ฒํผ์ ํด๋ฆญํ๋ ๋ฉ์๋๋ฅผ handleCreateClick์ด๋ผ๊ณ ์ ํ๋ค.
// ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ด์ ์ด ๋ฉ์๋๊ฐ ์์ฐจ์์ฐจ ๋์๊ฐ๋ค.
const handleCreateClick = async () => {
// ์์ 3๊ฐ์ง๋ฅผ ์ ํํ๋์ง ํ์ธํ๋ค.
if (selectedOptions.length !== 3) {
showMagicToast('์์ 3๊ฐ์ง๋ฅผ ์ ํํด์ฃผ์ธ์.');
return;
}
try {
setIsSubmitting(true);
// ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด fetch๊ฐ ์คํ๋๋ค.
// ์๋๋ผ๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์ง์ง ์๋ฒ ์ฃผ์๋ฅผ ์ฐพ์ ๋๊ฐ๋ค.
// ๊ทธ๋ฐ๋ฐ MSW๊ฐ ์ด ์์ฒญ์ ์ค๊ฐ์์ ๋์์ฑ๋ค.
const response = await fetch('/api/lotto/draw', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
selectedOptions,
}),
});
// MSW๊ฐ ์ค ๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ๋ฐ์๋ค.
// result ๋ณ์์๋ ํธ๋ค๋ฌ์ ์ ์ด๋์๋ ๋ก๋ ๋ฒํธ, ํ์ด ์ ์ ๋ฑ์ด ๋ด๊ธด๋ค.
const result: DrawResponse = await response.json();
// ๋น์ฅ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ์ง ์๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ณผ๊ฐ์ sessionStorage์ ๋ณด๊ดํ๋ค.
// ๋ธ๋ผ์ฐ์ ๊ฐ ๋ซํ๊ธฐ ์ ๊น์ง ๋ณด๊ด์ ์ ์ง๋๋ค.
sessionStorage.setItem('lotto-result', JSON.stringify(result));
// ๊ทธ ํ ๋ก๋ฉํ์ด์ง๋ก ๋์ด๊ฐ๋ค.
router.push('/loading');
- ResultPage.tsx(๊ฒฐ๊ณผํ์ด์ง)_ ์ธ์ ์คํ ๋ฆฌ์ง์ ์๋ ๊ฒฐ๊ณผ ๋ณด์ด๊ธฐ

useEffect(() => {
const savedResult = sessionStorage.getItem('lotto-result');
์๊น HomePage.tsx์์ sessionStorage์ ๊ฒฐ๊ณผ๊ฐ์ ์ ์ฅํด๋๋ค. ๊ทธ ์ ์ฅํด๋ ๊ฐ์ ๊ฐ์ ธ์์ ํ๋ฉด์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค๋ค.
- MSW (React+TypeScript) ์ค์น
React์์ MSW ์ฌ์ฉํ๊ธฐ
์ค๋์ ์ด์ง์ ์ค๋นํ๋ฉด์ ๊ณผ์ ์ ํ์์ ์ฒ์ ๋ค๋ค๋ณด์๋ msw๋ผ๋ ๊ฒ์ ์์๋ณด์.๊ณผ์ ์ ํ์ ๋ฐ์์๋ ๊ธฐ์ด ํ ํ๋ฆฟ๊ณผ ๊ณผ์ ์ค๋ช ์ด ๋ค์ด๊ฐ ํ ํ๋ฆฟ์ ๋ฐ์๋๋ฐ, ์ ์ผ ๋นํฉํ๋ ๋ถ๋ถ์ api์ end-po
velog.io
MSW(Mock Service Worker)๋ก ๋์ฑ ์์ฐ์ ์ธ FE ๊ฐ๋ฐํ๊ธฐ
MSW(Mock Service Worker)๋ Service Worker๋ฅผ ์ด์ฉํด ์๋ฒ๋ฅผ ํฅํ ์ค์ ๋คํธ์ํฌ ์์ฒญ์ ๊ฐ๋ก์ฑ์(intercept) ๋ชจ์ ์๋ต (Mocked response)๋ฅผ ๋ณด๋ด์ฃผ๋ API Mocking ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
velog.io