์ผ๋ง ์ ์ ์ด๋ฐ ๋ด์ค๋ฅผ ๋ณด๊ณ , ์ธ์ ๊ฐ ์๋ถ์ฌ(?)์ ๊ฐ์ง ์๊ฒฉ์ ์ฆ๋ช ํ ์ ์๋ ๋ฌด์ธ๊ฐ๊ฐ ์๊ธฐ๋ ๊ฒ ์๋๊น ํ๋ ์์์ ํด๋ดค์ด์.
๋ง์นจ ์ง์ธ์ด ๋ฐฑ์ ์ ๋ง๊ณ ๋์ ์ฑ์ผ๋ก ์๋ฐฉ์ ์ข ์ฆ๋ช ์๋ฅผ ๋ฐ์๋ค๊ณ ํ ๊ฒ ๋ ์ฌ๋ผ์, ์ด๊ฒ๋ ์ฆ๋ช ์ ์ฑ/์น์ ํํ๋ก ํจ๋ฌ๋ํด์ ๋ง๋ค๋ฉด ์ฌ๋ฏธ์์ ๊ฒ ๊ฐ์์ฃ !
๋ค ๋ง๋ค๊ณ ๋์ ๋ณด๋ ์๊ฐ๋ณด๋ค ๋ง์ ์๊ฐ์ ๋ณด๋ธ ๊ฒ ๊ฐ์ง๋ง, ์ฒ์์๋ ์ต๋ํ ๋น ๋ฅด๊ฒ ๋ง๋ค๊ณ ์ถ์๊ธฐ ๋๋ฌธ์ ์ฆ๋ช ์? ๊ทธ๋ฅ ์ด๋ฏธ์ง๋ก ๋์ถฉ ๊ทธ๋ ค์ ๋ฃ๊ณ ํ์ด์ง ์ค์ ์ ๋ ฌ๋ง ์์ผ์ ๋ณด์ฌ์ฃผ์! ํ์ต๋๋ค.
์ ๋ ๋ฐฑ์ ์ ๋ชป ๋ง๊ณ ์๊ธฐ ๋๋ฌธ์...! ์ธํฐ๋ท์์ ๊ตด๋ฌ๋ค๋๋ ์๋ฐฉ์ ์ข ์ฆ๋ช ์์ ์คํฌ๋ฆฐ์ท(์์ ์ฒจ๋ถํ ์ด๋ฏธ์ง์ ๊ฐ์ด ํ ์คํธ์ฉ ์ฑ์ ์คํฌ๋ฆฐ์ท์ ์ฌ์ฉํ์ต๋๋ค)์ ๊ตฌํด ์์ ๋๊ณ ๋น์ทํ ๋๋์ผ๋ก ํ๋ ๊ทธ๋ ค๋ดค์ต๋๋ค... ๐
์ด๋ฏธ์ง๋ฅผ ํ์ํ๊ณ , ํ๋ฉด ์ค์์ ์ ๋ ฌ์์ผ ๋ดค์ด์. ํ ๋น์ด ์์ด์ ๋ง์ด ์ธ๋ก์ ๋ณด์ด๋ค์.
width: 300px;
border-radius: 8px;
box-shadow: 0px 16px 36px rgba(0, 0, 0, 0.05);
@media (max-width: 400px) {
width: 285px;
}
์ ๋ ์ฆ๋ช
์ ํ๋ฉด์ ๋ค์ด๊ฐ์๋ง์ ์ด๋ชจ์ง๊ฐ ์์์ ธ ๋ด๋ฆฌ๊ธธ ์ํ์ด์!
์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋ ํ๊ณ ๊นํ๋ธ์์ ์ฌ๋ฌ ๊ฐ์ ๋ชจ๋์ ๋น๊ตํด ๋ดค์๋๋ฐ์.
js-confetti๊ฐ ๊ฐ์ฅ ๊น๋ํ ๊ฒ ๊ฐ์์ด์. ์ฒ์์๋ react-rewards๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ํ์๋๋ฐ, ์ด ๋ชจ๋์ ์คํ๋ ํญ์ฃฝ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค๋ ์ฌ์ฉ์์ ์ธํฐ๋ ์
์ ๋ฐ๋ฅธ ๋ณด์์ด๋ ์ฒ๋ฒ์ ๋ด๋ฆฌ๋ ์ญํ (์ค์ ๋ก ๋ฉ์๋ ์ด๋ฆ์ด rewardMe
, punishMe
๋ค์ ใทใท)์ด ๋ ์ ์ ํด ๋ณด์์ฃ . ์ฌ์ค ์คํฌ๋กค ๋ฒ๊ทธ๊ฐ ํ๋ ์์๋๋ฐ ๊ณ ์น๊ธฐ ์ด๋ ค์์ ํจ์คํ์ต๋๋ค.
export const Card: React.FC<CardProps> = () => {
useEffect(() => {
const confetti = new JSConfetti();
confetti.addConfetti({
emojis: ['๐ฐ๐ท', '๐ธ', '๐ต', '๐'],
emojiSize: 256,
confettiNumber: 30,
confettiRadius: 6,
});
}, []);
return (
<CardContainer>
<CardImage src="/images/card.svg" />
</CardContainer>
);
};
์ด๋ ๊ฒ ์ฒ์ ํ ๋ฒ๋ง JSConfetti
ํด๋์ค๋ฅผ ๋ง๋ค๋ฉด, Canvas ์๋ฆฌ๋จผํธ๋ฅผ ํ์ฌ document
์ ์ถ๊ฐํ๋ฉด์ ๋ชจ๋์ด ์ด๊ธฐํ๋ฉ๋๋ค. ๊ทธ ๋ค๋ก addConfetti
๋ฉ์๋๋ฅผ ํธ์ถํ ๋๋ง๋ค ํญ์ฃฝ์ด ํฐ์ง์ฃ !
์ ๊ทธ๋ฐ๋ฐ ์ ๋ Next.js๋ฅผ ์ฌ์ฉํ๊ณ ์์๋๋ฐ์. ์ ๋ ๊ฒ ์ฝ๋๋ฅผ ์ง๋ ์๋ฒ์ฌ์ด๋์์ ๋ ๋๋ง๋ ๋ new JSConfetti()
๊ฐ ์คํ๋๋ฉด์ ReferenceError: document is not defined
์๋ฌ๊ฐ ๋ฐ์ํ๋ ๋ฌธ์ ๊ฐ ์์์ด์.
useEffect(() => {
if (typeof document === 'undefined') {
return
}
// client only
const confetti = new JSConfetti();
confetti.addConfetti({
emojis: ['๐ฐ๐ท', '๐ธ', '๐ต', '๐'],
emojiSize: 256,
confettiNumber: 30,
confettiRadius: 6,
});
}, []);
Node ํ๊ฒฝ์์๋ document
global์ด ์ ์ธ๋์ง ์์๊ธฐ ๋๋ฌธ์ ์๊ธฐ๋ ๊ฒ์ด์ฃ ! ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์๋ง ์คํํ๊ธฐ ์ํด์๋ ์ ์ฝ๋์ฒ๋ผ typeof document
๋ก ๋ถ๊ธฐํ ์ ์๊ฒ ๋ค์.
์์ ๋ฐฐ๊ฒฝ๋ ๋ฃ์ด์ค๋๋ค. ์ด์ ๋๋์ด ์ข ์ฆ๋ช ์ ๊ฐ์ ๋๋์ด ๋ฉ๋๋ค!
const Container = styled.div`
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-image: url('/images/gradient.webp');
background-size: cover;
`;
๊ทธ๋ฐ๋ฐ ์์ง ๋ญ๊ฐ ์ฐ๋ ํ ๋๋์ด ๋ค์ด์, ๋ญ ํ๋ฉด ์ข์๊น ์๊ฐํ์๋๋ฐ์.
์ธ์ฆ์๋ฅผ ํด๋ฆญํ ๋๋ง๋ค 25๋ง์(์ฌ๋์ง์๊ธ ์ก์)์ฉ ์นด์ดํธ๊ฐ ์ฌ๋ผ๊ฐ๋(๋์ด ์ฐํ๋?) ๊ฒ ๋ณด์ด๋ฉด ์ด๋จ๊น ํด์ ๊ทธ๋ฅ ๊ทธ๋ ๊ฒ ํ๊ธฐ๋ก ํ์ต๋๋ค.
๋ณ ๋ป์ด ์๋ ๋์์ ์๋์ง๋ง ๋ณด์๋ ๋ถ๋ค๊ป์ ์ฒ์ 3์ด ๋์์ด๋ผ๋ ๊ทธ๋ฅ ์ฌ๋ฏธ์์ด์ ๋ช ๋ฒ ๋๋ฅผ ๊ฒ ๊ฐ์์ด์.
useCount
๊ตฌํํด๋ฆญ์ ๋๊ตฐ๊ฐ์ ๋
ธ๋ ฅ์ด์ฃ ! ์ฌ์ดํธ๋ฅผ ์๋ก๊ณ ์นจํ๋ ๋ฐ๋์ ์ํ๊ฐ ๋ชจ๋ ๋ ์๊ฐ๋ฉด ๋๋ฌด ์ฌํ๊ธฐ ๋๋ฌธ์ ์ด๋ฒ์๋ ๊ทธ ๊ฐ์ localStorage
์ ์ ์ฅํ๊ธฐ๋ก ํ์ต๋๋ค.
export const useCount = (): [number, () => void] => {
const [count, setCount] = useState<number>(() => {
if (typeof localStorage === 'undefined') {
return 0;
}
const storedNumber = parseInt(localStorage.getItem('@count'));
return storedNumber || 0;
});
const updateCount = useCallback(() => {
const nextCount = count + 1;
localStorage.setItem('@count', nextCount.toString());
setCount(nextCount);
}, [count]);
return [count, updateCount];
};
localStorage
๋ ๋ฌธ์์ด ํค์ ๋ฌธ์์ด ๊ฐ์ ๋งคํํด ์ฃผ๊ธฐ ๋๋ฌธ์, ํ์ฌ count
์ํ๋ฅผ ์
๋ฐ์ดํธํ ๋๋ง๋ค ๊ทธ ๊ฐ์ String
์ผ๋ก ๋ฐ๊ฟ ์ ์ฅํ๋๋ก ํ์ด์. ์ฒ์์ ์ ์ฅ๋ ์ํ๊ฐ ์๋์ง ํ์ธํ๊ณ , ๊ฐ์ ธ์ฌ ๋๋ ๊ทธ ๊ฐ์์ ํ์ฑํ๊ณ ์!
๋ง๋ ํ ์ ์ด๋ ๊ฒ ์ฌ์ฉํ ์ ์์ด์!
const [clicks, updateClicks] = useCount();
const message = useMemo(() => {
if (!clicks) {
return '์ธ์ฆ์๋ฅผ ๋๋ฌ ๋์ ์ฐ์ด ๋ณด์ธ์.';
}
return `${convert(',$.3s', STIMULUS * clicks)}์ ์ฐ์ด๋ด์
จ๋ค์.`;
}, [clicks]);
...
<Card onClick={updateClicks} />
์ฌ๊ธฐ์ convert
ํจ์๋ uck์ด๋ผ๋ ๋ชจ๋์ ๊ฒ์
๋๋ค. ์ซ์๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ํ๊ธ๋ก ์ฝ์ด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
,$.3s
๋ฌธ์์ด์ convert
ํจ์์ ์กฐ๊ฑด์ ๋ํ๋
๋๋ค(์ ๋งํฌ README ์ฐธ๊ณ ).,
: ์ฒ ์๋ฆฌ๋ง๋ค ์ฝค๋ง(,
)๋ฅผ ํ์ํ ๊ฒ$
: ์์ฑ๋ ํ๊ธ ๋ฌธ์์ด์ ์
๋จ์๋ฅผ ๋ถ์ผ ๊ฒ.3
: precision ์ ์ค์ (์ฌ๊ธฐ์ ํฌ๊ฒ ์ค์ํ์ง ์์์ ํจ์คํ๊ณ ์์ ๊ทธ๋๋ก ๋ถ์๋ ๊ฒ ๊ฐ์์)s
: ๋จ์๋ง๋ค ๊ณต๋ฐฑ ํ ์นธ ์ถ๊ฐ(space
์ s
๋ฅผ ์๋ํ์ ๋ฏ)uck
์ ๋ชจ๋ฅด๋ ์ฌ๋์ด ํ๋์ ์๊ธฐ๋ ์ข ์ด๋ ต์ง ์๋ ์ถ๋ค์. ์๋์ฒ๋ผ ๊ทธ๋ฅ ์ต์
์ ๋ค๋ฃจ๋ Object๋ฅผ ์ ๋ฌํ ์ ์์๋ค๋ฉด ํจ์ฌ ์ ์ฝํ๊ธฐ๋ ํ๊ณ TypeScript ๋ชจ๋์ ์ฅ์ ์ ๋์ฑ ํ์ฉํ ์ ์์์ ๊ฒ ๊ฐ์์.convert(value, {
suffix: '์',
precision: 3,
addComma: true,
addSpace: true,
})
ํด๋ฆญํ ๋๋ ์ด๋ชจ์ง ํญ์ฃฝ์ ํฐ๋จ๋ฆฌ๊ฒ ํ๊ณ , ์ฝ๊ฐ์ ์ ๋๋ฉ์ด์ ๊ณผ ์คํ์ผ์ ๋ํ์ ์๋ ๊ฐ์ ๊ฒฐ๊ณผ๋ฌผ์ด ๋์์ด์!
์๋ถ์ฌ์ ์ค์ค๋ก ๊ฐ์ง๋ ๊ฒ์ ๋๋ค.
์ฝ์ด์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค ๐
์๋น์ค๋ https://pride-stimulus.vercel.app ์์,
์ฝ๋๋ https://github.com/junhoyeo/pride-stimulus ์์ ํ์ธํ์ค ์ ์์ต๋๋ค!
์ฌ์น์๋ ํ๋ก์ ํธ๋ผ ์๊ฐํฉ๋๋ค. ํ๋ก๊ทธ๋๋จธ์ด์๋ฉด์ ์ํฐ์คํธ์ด์๋ค์
์ฌ๋ฏธ์๊ฒ ์ญ ๋ดค์ต๋๋ค :)
์ค... ์ฑ ๋ฒ์ ์ผ๋ก ๋ง๋ค์ด๋ณด๊ณ ์ถ์๋ฐ ๊ด์ฐฎ์๊น์? ใ ใ
๊ณ์ ๋๋ฌ์ 1์ฒ๋ง์๊น์ง ์ฌ๋ ธ์ด์!
๋์ ๊ฐ๋ฐ์๋์ด ์ฃผ์๋ ๊ฑฐ์ฃ ?
์ ๊ณ์ข๋ ์ ํ 110-446..
์ค.... ์ ๋ ๊ณ ์๋์ ๋์ ๊ณ ์๋์์ ์๋ถ์ฌ์ข ๊ฐ์ ธ๋ณด๊ณ ์ถ์ด์ ใ ใ ใ ใ