์ฌ์ฉ์ ์ ํ ์ต์ ์ ๊ฒ์ฌํ๊ณ
๋ก๋ ๊ฒฐ๊ณผ ์๋ต์ ๊ฒ์ฆํ๊ธฐ ์ํด
Zod ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ค.
Zod๊ฐ ์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ์ง ์์๋ณด๊ณ
์ฝ๋ ์์ฑ์ ํด๋ณด์
- ๋ก๋ ๋ฒํธ ์์ฑ ๋ง๋ฒ์ง
๋ก๋ ๋ฒํธ ์์ฑ ๋ง๋ฒ์ง
ํ์ด ์์ 3๊ฐ๋ฅผ ์ ํํ๋ฉด ์ค๋์ ๋ก๋ ๋ฒํธ์ ํ์ด ์ ์๋ฅผ ๋ง๋ค์ด์ฃผ๋ ์ฌ์ดํธ์ ๋๋ค.
lotto-magic-frontend.vercel.app
1. Zod
- TypeScript๋ฅผ ์ํ ์คํค๋ง ์ ์ธ ๋ฐ ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
- ๋ฐ์ดํฐ๊ฐ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ํํ์ ๊ท์น์ ์ ํํ ๊ฐ์ถ๊ณ ์๋์ง ๊ฒ์ฌํด์ฃผ๋ ๋๊ตฌ์ด๋ค.
- TypeScript๋ ์ปดํ์ผ ํ์์๋ง ํ์ ์ ๊ฒ์ฌํด์ค๋ค.
- ์ฝ๋๊ฐ ๋ณํ๋์ด ์ค์ ๋ก ์น๋ธ๋ผ์ฐ์ ๋ ์๋ฒ์์ ์คํ๋ ๋๋ ์์ํ JavaScript๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ํ์ ๊ฒ์ฌ๊ฐ ๋ถ๊ฐํ๋ค.
- ๋ง์ฝ ์๋ฒ API์์ ์ซ์๋ฅผ ๋ณด๋ด์ฃผ๊ธฐ๋ก ํ๋๋ฐ ์ค์๋ก ๋ฌธ์๋ฅผ ๋ณด๋์ ๊ฒฝ์ฐ, TypeScript๋ง์ผ๋ก๋ ์คํ ์ค์ ์ฑ์ด ๊ณ ์ฅ๋๋ ๊ฒ์ ๋ง์ ์ ์๋ค.
- ๊ทธ๋์ Zod๋ ์ด ํ๋ก๊ทธ๋จ ์คํ ์ค์ ๋ฐ์ดํฐ๊ฐ ์ฌ๋ฐ๋ฅธ์ง ๊ฒ์ฌํด์ฃผ๋ ์ญํ ์ ํ๋ค.
- Zod ์ฌ์ฉ ์์ : ์ธ๋ถ API ์๋ต ๋ฐ์ดํฐ ๊ฒ์ฆ, ์ฌ์ฉ์ ์ ๋ ฅ ํผ ๊ฒ์ฆ, ๋ฐฑ์๋ API ์์ฒญ ๊ฒ์ฆ, ํ๊ฒฝ ๋ณ์ ๊ฒ์ฌ ๋ฑ์ด ์๋ค.
2. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
npm install zod
3. lottoApi.ts
import { z } from 'zod';
z๋ Zod์ ๊ธฐ๋ฅ์ ๋ชจ์๋ ๊ฐ์ฒด์ด๋ค. importํ๋ฉด ์๋์ ๊ฒ๋ค์ ๋ง๋ค ์ ์๋ค.
z.string() z.number() z.object(...) z.array(...)โ
์ด ์ค์ z.string์ ์ด ๋ฐ์ดํฐ๋ ๋ฌธ์์ด์ด์ด์ผ ํ๋ค๋ ๊ฑธ ์ ์ธํ ๋ ์ฌ์ฉํ๋ค.
export const LottoOptionsResponseSchema = z.object({
options: z.array(z.string()),
});
์๋ฒ์์ ๋ก๋ ์ต์ ๋ชฉ๋ก์ ๋ฐ์์ฌ ๋์ ์๋ต ๋ชจ์์ ๊ฒ์ฌํ๋ ์คํค๋ง์ด๋ค.
{ options: ['๊ฐ๊ฟ', 'ํ์ด', '๋ด๋'] }โ
์ด๋ฐ ๋ฐ์ดํฐ๋ง ํต๊ณผํ๋๋ก ํ๋ค. ์๋๋ฉด z.object ๋๋ฌธ์ธ๋ฐ, z.object๋ ์ด ๋ฐ์ดํฐ๋ ๊ฐ์ฒด์ฌ์ผํ๋ค๋ ์๋ฏธ์ด๋ค. ๊ทธ๋์ { } ์ด๋ฐ ํํ์ฌ์ผ ํ๋ค.
Zod๋ ์ฝ๋๊ฐ ์ง๊ด์ ์ด๋ค. ๋ณด๋ฉด z.object๋ { }์ ์๋ฏธํ๊ณ options๊ฐ ์์ด์ผํ๋ฉฐ ๊ทธ ์์๋ ๋ฌธ์์ด๋ก ์ด๋ค์ง ๋ฐฐ์ด์ด ์์ผํ๋ค๊ณ ์ ์ํ๊ณ ์๋ค. ๊ทธ๋์ ์ค์ ๋ก ๋ฐ์์ฌ ๋ ์๋ต ๋ชจ์์ ์ดํด๋ณด๋ฉด options: ['๋ฌธ์์ด1', '๋ฌธ์์ด2', '๋ฌธ์์ด3'] ์ด๋ฐ ๋ฐฐ์ด ๊ตฌ์กฐ๊ฐ ๋๋ค.
export const DrawLottoRequestSchema = z.object({
selectedOptions: z.array(z.string()).length(3),
});
๋ก๋ ๋ฒํธ๋ฅผ ๋ฝ์ ๋ ์๋ฒ๋ก ๋ณด๋ด๋ ์์ฒญ ๋ฐ์ดํฐ์ ๋ชจ์์ด๋ค. ์์ ์ฝ๋์ ๋ค๋ฅธ ์ ์ lengh(3)์ด ๋ถ์ ๊ฒ์ด๋ค. ๋ฐฐ์ด์ ๊ธธ์ด๊ฐ ์ ํํ 3๊ฐ์ฌ์ผ ํ๋ค๊ณ ์๋ฏธํ๋ค. ๋ง์ฝ 2๊ฐ๋ 4๊ฐ์ ๋ฐฐ์ด์ด ์จ๋ค๋ฉด ๊ทธ๊ฑด ์คํจํ๋ค.
export const LottoDrawResponseSchema = z.object({
numbers: z
.array(z.number().int().min(1).max(45))
.length(6)
.refine(
(numbers) => new Set(numbers).size === numbers.length,
{
message: '๋ก๋ ๋ฒํธ๋ ์ค๋ณต๋ ์ ์์ต๋๋ค.',
}
),
luckScore: z.number().int().min(0).max(100),
luckMessage: z.string(),
selectedOptions: z.array(z.string()).length(3),
spellNumber: z.number().int().min(1).max(9),
spellImageUrl: z.string(),
});
์๋ฒ๋ ์๋์ ๊ฐ์ ๋ชจ์์ผ๋ก ์๋ต์ ํด์ผํ๋ค.
{ numbers: [3, 11, 18, 24, 36, 42], luckScore: 87, luckMessage: '์ฐ์ฃผ ํต์ ์ฐ๊ฒฐ ์๋ฃ', selectedOptions: ['๊ฐ๊ฟ', 'ํ์ด', '๋ด๋'], spellNumber: 5, spellImageUrl: '/images/spell5.png' }
์ ์ด๋ ๊ฒ ๋๋์ง ์ฝ๋๋ฅผ ์์ธํ ์ดํด๋ณด์.
numbers: z .array(z.number().int().min(1).max(45)) .length(6) .refine(...)
๋ก๋ ๋ฒํธ 6๊ฐ๋ฅผ ๊ฒ์ฌํ๋ค. numbers๋ ๋ฐฐ์ด ๊ตฌ์กฐ์ด๋ฉด์, 1๋ถํฐ 45๊น์ง ์ด๋ค์ง ์ซ์๋ก ์ ์๋ง ํ์ฉํ๋ค. ๋ฐฐ์ด ์ ์ฒด ๊ธธ์ด๋ ์ ํํ 6๊ฐ์ฌ์ผ ํ๋ค..refine( (numbers) => new Set(numbers).size === numbers.length, { message: '๋ก๋ ๋ฒํธ๋ ์ค๋ณต๋ ์ ์์ต๋๋ค.', } )
.refine( )์ Zod๊ฐ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ ๊ฒ์ฌ๋ง์ผ๋ก ๋ถ์กฑํ ๋ ์ง์ ์กฐ๊ฑด์ ์ถ๊ฐํ๋ ๊ธฐ๋ฅ์ด๋ค. ์ฌ๊ธฐ์ ์ค๋ณต ๋ฒํธ๊ฐ ์์ด์ผํ๋ค๋ ๊ท์น์ ์ง์ ๋ง๋ค์๋ค.(๋ก๋๋ ์ค๋ณต ๋ฒํธ๊ฐ ์์ผ๋) numbers์๋ ์์์ ๊ฒ์ฆ๋ ๋ฐฐ์ด์ด ๋ค์ด์จ๋ค. ์ด ๋ฐฐ์ด์ new Set์ผ๋ก ๋ฐ๊พธ๋ฉด ์ค๋ณต์ด ์ ๊ฑฐ๋๋ค. ๋ง์ฝ [1,2,3,4,5,5]๊ฐ ๋ค์ด์๋ค๊ณ ๊ฐ์ ํ์. set์ผ๋ก ๋ฐ๋๋ฉด [1,2,3,4,5]๊ฐ ๋๋ค. ๊ทธ๋์ ๊ฒฐ๊ตญ size(5)๊ฐ number.length(6)์ ๋ง์ง ์๊ฒ๋์ด์ ๊ฒ์ฆ์ ์คํจํ๊ฒ๋๋ค.
๊ทธ ๋ค์ ์ฝ๋๋ ๋น์ทํ ๊ตฌ์กฐ์ด๋ ๋์ด๊ฐ๊ฒ ๋ค.
export type LottoOptionsResponse = z.infer<typeof LottoOptionsResponseSchema>;
export type DrawLottoRequest = z.infer<typeof DrawLottoRequestSchema>;
export type LottoDrawResponse = z.infer<typeof LottoDrawResponseSchema>;
์ด ๋ถ๋ถ์ Zod ์คํค๋ง๋ฅผ TypeScript ํ์ ์ผ๋ก ๋ฐ๊ฟ์ฃผ๋ ์ฝ๋๊ฐ ๋๋ค. z.infer<typeof Schema>๋ฅผ ์ฌ์ฉํ๋ฉด ์คํค๋ง ์ ์์์ ์ ์ TypeScript ํ์ ์ ์ถ์ถํ ์ ์๋ค๊ณ ํ๋ค.
export const DrawLottoRequestSchema = z.object({ selectedOptions: z.array(z.string()).length(3), });โ์ด ์ฝ๋๋ฅผ Zod๊ฐ TypeScript ํ์ ์ผ๋ก ๋ง๋ค์ด์ ์๋์ ๋น์ทํ ํ์ ์ด ๋๋ค.
type DrawLottoRequest = { selectedOptions: string[]; }โ
Zod์ ํฐ ์ฅ์ ์ ์คํค๋ง๋ฅผ ๊ณ ์น๋ฉด ์์์ ํ์ ์ด ์๋์ผ๋ก ๋ฐ๋ผ ๊ณ ์ณ์ง๋ค๋ ๊ฒ์ด๋ค.
// Primses๋ ๋์ค์ ๊ฒฐ๊ณผ๊ฐ ๋ค์ด์ค๋ ๊ณณ์ผ๋ก
// ์ฝ๊ฒ ์ค๋ช
ํ๋ฉด ๋์ค์ ๊ฒฐ๊ณผ ์ค๊ฒ ํ๊ณ ์ฝ์ํ๋ ๋๋์ผ๋ก ์ฌ์ฉํด์ Promise๋ฅผ ๋ฐํํ๋ค.
// ์ ๋์ค์ ๋ฐ๋? fetch ๋๋ฌธ์ด๋ค.
// ์๋ฒ ์๋ต์ ๋ฐ๋ก ์ค์ง ์์์, ์๋ต์ด ์ฌ ๋๊น์ง ๋ฉ์ถฐ์์ง ์๊ณ Promise๋ก ์์ฝ์ ๊ฑธ์ด๋๋ค.
export async function getLottoOptions(): Promise<LottoOptionsResponse> {
const response = await fetch(createApiUrl('/api/lotto/options'));
if (!response.ok) {
const message = await getErrorMessage(response);
throw new Error(message);
}
const data = await response.json();
return LottoOptionsResponseSchema.parse(data);
}
์ฌ๊ธฐ์ Zod๊ฐ ์ค์ ๋ก ์ผ์ ํ๋ค.
const data = await response.json();โ
์๋ฒ์์ ๋ฐ์ JSON์ JavaScript ๊ฐ์ฒด๋ก ๋ฐ๊ฟ์ค๋ค. ๊ทธ๋ฐ๋ฐ ์ด๋์ data๋ ๋ฏฟ์ ์ ์๋ ๋ฐ์ดํฐ์ด๋ค. ๊ทธ๋์ ์ด์ ์ด ์ฝ๋๋ฅผ ๊ฒ์ฌํด์ผํ๋ค.LottoOptionsResponseSchema.parse(data)
.parse( )๋ ์ ๋ ฅ๊ฐ์ ์คํค๋ง์ ๋ง๊ฒ ๊ฒ์ฆํ๊ณ ๋ง์๋ฉด ํ์ ์ด ๋ณด์ฅ๋ ๊ฐ์ ๋ฐํํ๋ค. ๊ฒ์ฆ์ ์คํจํ๋ฉด ZodError๋ฅผ ๋์ง๋ค.
lotto-magic-frontend/lib/api/lottoApi.ts at main · hyeong-ing/lotto-magic-frontend
๋ก๋ ๋ง๋ฒ์ง ์์ฑ ์น์ฌ์ดํธ_ ํ๋ก ํธ ์ฝ๋. Contribute to hyeong-ing/lotto-magic-frontend development by creating an account on GitHub.
github.com
4. ResultPage.tsx
import {
LottoDrawResponseSchema,
type LottoDrawResponse,
} from '@/lib/api/lottoApi';
ResultPage๋ Zod์ ์ด๋ ๊ฒ ์ฐ๊ฒฐ์์ผฐ๋ค. ์๊น ๋ง๋ค์๋ lottoApi์์ Zod ์คํค๋ง์ Zod๋ก ๋ง๋ ํ์ ์ ๊ฐ์ ธ์์ ์ฌ์ฉํ ๊ฒ์ด๋ค.
useEffect(() => {
// ๊ฒฐ๊ณผ๋ฅผ ์ ๊น sessionStorage๋ก ์ ์ฅํ๋ค.
// ์๋ก๊ณ ์นจํด๋ ๊ฐ์ ํ๋ฉด์ด ๋ณด์ด๋๋ก ํ๋ค.
const savedResult = sessionStorage.getItem('lotto-result');
// ์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ฉด ํ ์คํธ ๋ํ๋๊ณ
// ๋ฃจํธ ํ๋ฉด์ผ๋ก ์ด๋์ํจ๋ค.
if (!savedResult) {
showMagicToast('์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ ์์ด์.');
router.replace('/');
return;
}
try {
//
const parsedJson = JSON.parse(savedResult);
const safeResult = LottoDrawResponseSchema.parse(parsedJson);
setResult(safeResult);
} catch (error) {
// ์๋ฌ ๋ฐ์์ ์ฝ์์ ์ด๋ค ์๋ฌ์ธ์ง ๋ณด์ฌ์ค๋ค.
console.error(error);
// ๊ทธ๋ฆฌ๊ณ sessionStorage์ ์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ์ ์ง์ด๋ค.
sessionStorage.removeItem('lotto-result');
// ํ ์คํธ๋ก ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๊ณ
showMagicToast('๊ฒฐ๊ณผ ์ ๋ณด๋ฅผ ์ฝ์ง ๋ชปํ์ด์.');
// ๋ฃจํธ ํ๋ฉด์ผ๋ก ๋ณด๋ธ๋ค.
router.replace('/');
}
}, [router]);
const parsedJson = JSON.parse(savedResult); const safeResult = LottoDrawResponseSchema.parse(parsedJson); setResult(safeResult);
Zod๊ฐ ์ค์ ๋ก ๋์ํ๋ ๋ถ๋ถ์ ์์ ์ฝ๋์ด๋ค. sessionStorage์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ ๋ฌธ์์ด์ด๋ค.'{"numbers":[1,2,3,4,5,6],"luckScore":80,...}'
๋์ถฉ ์ด๋ฐ์์ผ๋ก ์ ์ฅ๋์ด ์์ ๊ฒ์ด๋ค. ์ด๋ ๊ฐ์ฒด์ฒ๋ผ ๋ณด์ฌ๋ ๋ฌธ์์ด์ด๋ค.const parsedJson = JSON.parse(savedResult);
๊ทธ๋์ JavaScript ๊ฐ์ฒด๋ก ๋ฐ๊พธ๊ธฐ ์ํด ์์ ์ฝ๋๋ฅผ ์์ฑํ๋ค. JSON.parse( ) ๋ฉ์๋๋ JSON ๋ฌธ์์ด์ ๊ตฌ๋ฌธ์ ๋ถ์ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ์์ JavaScript ๊ฐ์ด๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.const safeResult = LottoDrawResponseSchema.parse(parsedJson);
์ด ์ฝ๋์์ Zod ์คํค๋ง๋ก ๊ฒ์ฌ๊ฐ ์ด๋ค์ง๋ค. parsedJson์ด LottoDrawResponseSchema ๊ท์น์ ๋ง๋์ง ๊ฒ์ฌํ๊ณ , ๋ง์ผ๋ฉด safeResult์ ์ฌ๋ฐ๋ฅธ ๋ก๋ ๊ฒฐ๊ณผ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๊ฐ๋ค.
JSON.parse() - JavaScript | MDN
developer.mozilla.org
lotto-magic-frontend/components/result/ResultPage.tsx at main · hyeong-ing/lotto-magic-frontend
๋ก๋ ๋ง๋ฒ์ง ์์ฑ ์น์ฌ์ดํธ_ ํ๋ก ํธ ์ฝ๋. Contribute to hyeong-ing/lotto-magic-frontend development by creating an account on GitHub.
github.com