์ ํฌํ (NDD)๋ ๋ฉด์ ์ฐ์ต ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๊ธฐ์ , ๊ธฐ์ ์ ์ ์ ํ๋ ์๊ฐ์ ๊ฐ์ง๊ฒ ๋์์ต๋๋ค.
์ ํฌํ (NDD)๋ ๋ฉด์ ์ฐ์ต ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๊ธฐ์ , ๊ธฐ์ ์ ์ ์ ํ๋ ์๊ฐ์ ๊ฐ์ง๊ฒ ๋์์ต๋๋ค. ๊ธฐ์ ์ ์ ์ ์์ด์ ๋ค์๊ณผ ๊ฐ์ ์ง๋ฌธ์ด ์ฃผ์ด์ก์ต๋๋ค.
- next๋ฅผ ์ฌ์ฉํด์ผํ๋๊ฐ?
- ์ด๋ค styled in JS ๋ฅผ ์ฌ์ฉํ ๊ฒ์ธ์ง
- ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ด๋ค๊ฒ?
์ ํฌ๋ ์๋์ ๊ฐ์ ๋ ผ์๋ฅผ ์งํํ๊ฒ ๋์์ต๋๋ค.
Next๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์ํ ํธ์์ ์ธ ๊ธฐ๋ฅ๋ค์ ์ฌ์ฉํ ์ ์๋ ์์ฃผ ๊ฐ๋ ฅํ ํ๋ ์์ํฌ์ ๋๋ค.
์ด๋ฒ ํ ๋ธ๋ก๊ทธ๋ SSG๋ฅผ ์ด์ฉํด์ Next๊ธฐ๋ฐ์ผ๋ก ๋ง๋ค์๊ธฐ๋ ํ์ต๋๋ค.
Next์ ์ฌ์ฉํ๋ฉฐ ํธ๊ฐ์ด ๋๋ ํ๋ ์์ํฌ ์ธ๋ฐ, ์ฌ๋ฌ๊ฐ์ง ํธ๋ฆฌํ ๊ธฐ๋ฅ๋ค์ด ์กด์ฌํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
โ next13 ๋ฒ์ ์ app Dir ๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด, page router ์ฒ๋ฆฌ์์ ์์ํ๊ฒ ๊ฐ๋๋ฐ์ ์ ์๋ค. ํน๋ณํ ๊ท์น์ ์ ์ฉํ์ง ์๋๋ผ๋(react-router ๋ฑ๋ฑ) ์ ๋ง ์ง๊ด์ ์ธ page ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ฉฐ, ๋ฐ๋ก ์ฝ๋ ์คํ๋ฆฟํ ์ด ์ด๋ฃจ์ด์ ธ ์ฑ๋ฅ์ ํฅ์์ํค๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋๋ฐ ํฐ ๋์์ด ๋๋ค๊ณ ์๊ฐํฉ๋๋ค. (ํ์ด์ง ๋ณ๋ก ํ์ํ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ง ๋ก๋ํ๋๊ฒ์ ํ๋ ์์ํฌ์์ ์ง์)
โ ์ผ๋ฐ์ ์ผ๋ก ์นํ์ด์ง์ ์ฑ๋ฅ์ ๋์ด์ฌ๋ฆฌ๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ค์ ์ด๋ฏธ์ง ์ต์ ํ๋ ์์ฃผ ํ์์ ์ธ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. Next๋ฅผ ๊ฐ๋ฐํ vercel๋ ์ด ๋ํ ์ ๋ ํด์ ๊ธฐ๋ฅ์ ๊ฐ๋ฐํ๊ฒ์ด ๋์ ๋๋ ๋ฐ์! ๋ํ์ ์ผ๋ก Lazy-Loading๊ณผ ์ด๋ฏธ์ง ์ฌ์ด์ฆ ์ต์ ํ ๋ฑ๋ฑ์ ํตํด์ ์ด๋ฏธ์ง๋ฅผ ํตํด์ ๋ฐ์ํ๋ ์ฌ๋ฌ ๋ฌธ์ ๋ฅผ ์์ฃผ ์ฝ๊ฒ ํด๊ฒฐํ ์ ์์ต๋๋ค.
https://fe-developers.kakaoent.com/2022/220714-next-image/
์นด์นด์ค ์ํฐํ ์ด๋จผํธ์์๋ Next/Image๋ฅผ ์ฌ์ฉํด ์์ฃผ ๊ฐ๋จํ๊ฒ ์ด๋ฏธ์ง ์ต์ ํ๋ฅผ ์ ์ฉํ๊ฒ์ด ๋์ ๋๋๋ค.
โ Next๋ ๋์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ์๋ฒ์ธก์์ HTML ์ ์์ฑํ๊ณ ํด๋ผ์ด์ธํธ์ ์ ์กํ๋ ๋ฐฉ์์ธ SSR์ ์ง์ํฉ๋๋ค. ์ด๋ ์์ฃผ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ด๋ฉฐ, SEO์ ๊ทนํ ์ ๋ฆฌํฉ๋๋ค. (SEO๋ "Search Engine Optimization"์ ์ฝ์๋ก, ์น์ฌ์ดํธ๋ ์นํ์ด์ง๋ฅผ ๊ฒ์ ์์ง์์ ๋ ์ ์ฐพ์ ์ ์๊ฒ ์ต์ ํํ๋ ๊ณผ์ ์ ๋๋ค. ์ด๋ ์น์ฌ์ดํธ์ ๊ฐ์์ฑ์ ๋์ด๊ณ , ๊ฒ์ ์์ง ๊ฒฐ๊ณผ ํ์ด์ง(SERP)์์์ ๋ญํน์ ํฅ์์์ผ, ๋ ๋ง์ ๋ฐฉ๋ฌธ์์ ํธ๋ํฝ์ ์ ๋ํ๋ ๋ชฉ์ ์ ๊ฐ์ง๋๋ค.)
์์ ์์ฑํ ๊ธฐ๋ฅ๋ค ๋ฟ์๋๋ผ ์ฌ๋ฌ ์ฅ์ ๋ค์ด ๋ค์ ํฌํจ๋์ด ์์ต๋๋ค. (/public ๊ฒฝ๋ก๋ฅผ ์ฐ์ ํด ์ด๋ฏธ์ง src ๊ด๋ฆฌ๊ฐ ํธ๋ฆฌ ๋ฑ๋ฑ)
ํ์ง๋ง ์ฐ๋ฆฌ๋ Next๋ฅผ ์ฌ์ฉํ์ง ์๊ธฐ๋ก ๊ฒฐ์ ํ๋๋ฐ, ์๋์ ๊ฐ์ ์ด์ ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
โ ์ฌ์ค ๊ฐ์ฅ ํฐ ์ด์ ๋ผ๊ณ ํ ์ ์๋ค. ํธ๋ฆฌํ ๊ธฐ๋ฅ์ด ๋ชจ์ฌ์ง ํ๋์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ๋ ์ข์ ์ฑ๋ฅ์ ์๋น์ค๋ฅผ ๋น ๋ฅธ ์์ผ ๋์ ๋ง๋ค์ด ๋ผ ์ ์์์ง๋ ๋ชจ๋ฅธ๋ค. ํ์ง๋ง ๊ทธ ๊ณผ์ ์์ ์ฐ๋ฆฌ์๊ฒ ๋จ๋๊ฒ์ด ์๋น์ค๋ง ์๊ธฐ๋ฅผ ๋ฐ๋ผ์ง๋ ์๋๋ค. ์ฐ๋ฆฐ ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ํ์ต์ ํตํด ์ง์์ ์ผ๋ก ์ฑ์ทจ๋ฅผ ์ด๋ค์ผ ํ๋ค๊ณ ์๊ฐํ๋ค. ํ๋ ์์ํฌ์์ ์ง์ํ๋ ์ด๋ฏธ์ง ์ต์ ํ๋ฅผ ์ง์ ๋ง๋ค์ด๋ณด๊ณ , ์ฝ๋ ์คํ๋ฆฟํ ์ ํด๋ณด๋ฉฐ ์ข ๋ ๊น์ด ์๊ฒ ์๋ฆฌ๋ฅผ ์ดํดํด ๋ณด๊ณ ์ถ๋ค. ์ฌ์ค ์ ๋์ ๋ฌธ์ ์ด๊ธด ํฉ๋๋ค. react์กฐ์ฐจ๋ ์ฌ์ฉํ์ง ์์์ผ๋ง ํ๋ค๊ณ ์๊ฐํ ์ ๋ ์์ผ๋ ๋ง์ ๋๋ค. ๊ทธ๋๋ ์ฐ๋ฆฐ react๋ฅผ ์ฌ์ฉํ๋ฉฐ ๋ ๊น๊ฒ FE์ ์ฌ๋ฌ ๋ถ์ผ์ ๋ํด์ ํ์ต์ ์งํํด ๋ณด๊ณ ์ถ์ต๋๋ค.
โ Next13์ด ์ ๋ฐ์ดํธ ๋๋ฉฐ ์ ๋ง ๋ค์ํ ๊ธฐ๋ฅ์ด ์ ๋ฐ์ดํธ ๋์์ต๋๋ค. ์ด์ ๊น์ง๋ client-component๊ธฐ๋ฐ์ด๋ Next๊ฐ default๋ฅผ server-component๋ก ๋ณ๊ฒฝํ๋ค๋ ์ ์ด ๊ฐ์ฅ ํฐ ์ ํ์ ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค. ํ์ง๋ง ์ฌ๊ธฐ์ ๋ค์ ์น๋ช ์ ์ธ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋๋ฐ, emotion์ ๋น๋กฏํ ์ฌ๋ฌ style in JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ค์ด ํธํ๋์ง ์๋ ๋ค๋ ์ ์ ๋๋ค.(๋ฌผ๋ก ๊ทธ๋ฐ ํ๋ฆ์ ๋ฐ๋ผ Next ๋ํ atomic css ๊ธฐ๋ฐ์ธ tailwind๋ฅผ ์ ๊ทน์ ์ผ๋ก ์ง์ํ๊ณ ์์ต๋๋ค. ) ์ด๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ๋ค์ ๋ํดํ ์ ํ ์ ์งํํ ํ ํ์ผ์ ์ต ์๋จ์ โuse clientโ๋ฅผ ๊ธฐ์ ํ๋ฉฐ ๋ค์ ์ด๊ธ๋ฆฌํ(์ง๊ทนํ ์ฃผ๊ด์ ์ ๋๋ค) ์ฝ๋๊ฐ ์์ฑ๋์ด์ผ๋ง ํฉ๋๋ค. ๋ฌผ๋ก app router๋ฅผ ์ฌ์ฉํด์ layout๋ฅผ ์ฌํ์ฉํ๋ ๋ถ๋ถ๊น์ง ํฌํจ๋์ด ์๋ค๋ณด๋, ์ด์ฏค ๋๋ฉด FE ๊ณต๋ถ๋ผ๊ธฐ๋ณด๋จ Next13์ ํ์ต์ ์น์ค๋๋ ๋ฏํ์ฌ ๋ฐฉํฅ์ฑ์ด ์๋ชป๋์๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค. (๋ฌผ๋ก app router๋ฅผ ์ค์ ํ์ง ์๋๋ค๋ฉด ์ด์ ๊น์ง์ next๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ง๋ง ์ด๋ ์คํ๋ ค ์ฌ์ฅ๋์ด ๊ฐ๋ ํ๋ ์์ํฌ์ ๋์ ๋ฉ๋ฌ๋ ค ์๋ ๋ฏํ ๊ธฐ๋ถ์ด ๋ค์์ต๋๋ค. Next 13์ app router๊ฐ stable ๊น์ง ๋์์ผ๋, ๋๋์ฑ ์ด์ ๋ฒ์ ์ ๋ํ ํ์ต์ ์์ฝ๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค. )
https://nextjs.org/blog/next-13-4
โ SSR์ ์ต๋ ์ฅ์ ์ด๋ผ๊ณ ํ ์ ์๋ ์ด๊ธฐ ๋ก๋ฉ ์๋์ Seo ์ ์ ๋ฆฌํ๋ค๋ ์ ๋ชจ๋ ์์ง ์ฐ๋ฆฌ ์๋น์ค์ ๋ํด์ ๋ถํ์ํ๋ค๊ณ ์๊ฐํ์ต๋๋ค. ์ ํฌ์ ์๋น์ค๋ Desktop ํ๊ฒฝ์ด ์์ ์ ๊ณต๋๊ธฐ ๋๋ฌธ์ ์์ง ์ด๊ธฐ ๋ก๋ฉ ์๋์ ๋ํด์ ๋ชจ๋ฐ์ผ์ ๋นํด ์ฌ์ ๋กญ๊ฒ ๋์ฒ๊ฐ ๊ฐ๋ฅํ ๊ฒ์ด๋ผ ์์ํ ์ ์์์ต๋๋ค. seo ๋ํ ssr์ด ์๋ csr ์ํ์์ ์ต์ ์ ๋คํด์ seo๋ฅผ ์ค๋นํ๋ ๊ฒฝํ์ ๊ฐ์ง๊ณ ์ถ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด ํธ์ด ์ฐ๋ฆฌ FE ํ์ด ์ฑ์ฅํ๋๋ฐ ํฐ ๋์์ด ๋๋ฆฌ๋ผ ์๊ฐํ์ต๋๋ค. ๋๋ถ์ด SSR์ด๊ธฐ ๋๋ฌธ์ ๋์ ์ผ๋ก ์๋ฒ๋ฅผ ์ด์ํด์ผ ํ๋ ๋ถ๋ด์ ๋์์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
๊ฒฐ๊ตญ ์ด์ ๊ฐ์ ์ด์ ๋ก ์ฐ๋ฆฌ๋ Next๋ฅผ ์ฌ์ฉํ์ง ์๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค!
์ฌ์ค ๋ง์ ์ ํ์ง๊ฐ ์์ง๋ ์์์ต๋๋ค. ์คํ๋ ค ๋ค๋ค ๋ช ํํ์ต๋๋ค.
์ฐ๋ฆฌ ํ์๋ค ๋ชจ๋ tailwind์ ๊ฐ์ atomic css ์ ์ต์ํ์ง๋ ์์๊ธฐ ๋๋ฌธ์ tailwind css๋ ๊ฐ์ฅ๋จผ์ ์ฐ์ ์์์์ ์ ๊ฑฐ๋์์ต๋๋ค (์ฌ์ฉํ ๊ฑฐ๋ฉด ์ฐจ๋ผ๋ฆฌ Next13์ app router๋ ํจ๊ป ์ฌ์ฉํ์งโฆ!)
ํ์๋ค ๋ชจ๋ styled-components๋ฅผ ์ฌ์ฉํด๋ณธ ๊ฒฝํ์ด ์์์ต๋๋ค.
๋ชจ๋๋ค styled-components์ ์ต์ํ์ผ๋ฉฐ, emotion์ ์ฌ์ฉํ๊ธฐ์ ํฐ ์ด๋ ค์์ด ์์๊ฒ ์ด๋ผ๋ ์๊ฐ์ด ๋ค์์ต๋๋ค.
๋ฌด์๋ณด๋ค ์ ๊ฐ emotion์ ์ฌ์ฉํด๋ณด๊ณ ์ถ์์ต๋๋ค.
์ ๊ฐ ์ฌ์ฉํด๋ดค๋ css-props์ ๋ํ ๊ฒฝํ์ด ์ข์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ ์ ๋๋ inline css ์ค๋ฌ์ด ์์ฑ์ด์์ผ๋, ๋ณด๋ค ์ง๊ด์ ์ด์์ผ๋ฉฐ ๋ ์ด์ styled-component์ ์ด๋ฆ์ ์ ํ๋ ๊ฒ์ ๊ณ ํต๋ฐ์ง ์์๋ ๋๋ค๋ ์ ์ด ๋งค๋ ฅ์ ์ด์์ต๋๋ค.
๊ฐ๋ฑ ํ๋ ์์ด ์ฐ๋ฆฌ๋ Emotion์ ์ ํํ๊ฒ ๋์์ต๋๋ค.
์ฌ์ค ๊ฒฐ์ ํ๊ธฐ ๊ฐ์ฅ ์ด๋ ค์ด ๋ถ๋ถ์ด์์ต๋๋ค.
์ ๋ ์ฌ๋ฌ๊ฐ์ง ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด๋ณธ์ ์ด ์์์ผ๋ฉฐ, ์ ์ผํ๊ฒ ์ฌ์ฉํด๋ณธ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Mobx์์ต๋๋ค.
Mobx์ ๋ํ ๊ฒฝํ์ ์ ๋ง ์ข์์ต๋๋ค. Mobx์ ์ค์ฌ์ด ๋๋ ์ฒ ํ์
"Anything that can be derived from the application state, should be derived. Automatically."
์ฆ, "์ ํ๋ฆฌ์ผ์ด์ ์ํ์์ ํ์๋ ์ ์๋ ๋ชจ๋ ๊ฒ์ ์๋์ผ๋ก ํ์๋์ด์ผ ํ๋ค"์ ๋๋ค. ์ด๋ ๊ฐ๋ฐ์๊ฐ ์ํ ๊ด๋ฆฌ์ ๋ํด ์ ๊ฒฝ ์ฐ์ง ์๊ณ ๋ UI๊ฐ ์ต์ ์ํ๋ฅผ ๋ฐ์ํ๋๋ก ํฉ๋๋ค. MobX๋ ๋ํ ํจ์จ์ ์ธ ์ ๋ฐ์ดํธ๋ฅผ ์ํด ์ต์ํ์ ์ฌ ๊ณ์ฐ๋ง ์ํํ์ฌ ์ฑ๋ฅ์ ์ต์ ํ ํ๋ ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค.
ํ์ง๋ง ๋ค๋ฅธ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ๊ฒฝํ์ ์ ํ ์์๊ธฐ ๋๋ฌธ์, ํ์๋ถ๋ค์ ๋ฏฟ๊ณ ํ์๋ถ๋ค์ recoil์ ์ฌ์ฉํด๋ณด์๋ ์๊ฒฌ์ ๋ฐ๋ฅด๊ฒ ๋์์ต๋๋ค.
recoil์ ์๋กญ๊ฒ ๋์ ํ๊ธฐ ์ ์ ์ ๊ฐ mobx๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋๋ฅผ ํ์ธํด ๋ณด๋ฉฐ, ๊ทธ ๊ฐ๋ ์ ๋ค์๊ธ ์ตํ๋ณด๋ คํ์ต๋๋ค.
์๋๋ ์ ๊ฐ ์ง๊ธ๊น์ง ์ฌ์ฉํ๋ mobx ๋ฅผ ์ฌ์ฉํ ๋์ ์ฝ๋์ ๋๋ค.
์ ์ฒด์ ์ธ Mobx ๋๋์ ๊ธฐ์ตํด๋ณด๋ ค ๋ค์ ๊บผ๋ด๋ดค์ต๋๋ค!
// MobX์ `observable`๊ณผ `toJS` ํจ์๋ฅผ ์ํฌํธํฉ๋๋ค.
import { observable, toJS } from "mobx";
// ToastProps์ ToastState ํ์
์ ์ํฌํธํฉ๋๋ค.
import type { ToastProps, ToastState } from "./type";
// `toastStore` ์ํ ์ ์ฅ์๋ฅผ ์์ฑํฉ๋๋ค. ์ด ์ํ๋ ๋ฐ์ํ์ผ๋ก ๊ด์ฐฐ ๊ฐ๋ฅํ ๊ฐ์ฒด์
๋๋ค.
export const toastStore = observable({
// `state` ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํฉ๋๋ค. ์ฌ๊ธฐ์๋ `toasts` ๋ฐฐ์ด๊ณผ `deviceType` ๋ฌธ์์ด์ด ํฌํจ๋ฉ๋๋ค.
state: {
toasts: [],
deviceType: "desktop",
} as ToastState,
// ์๋ก์ด ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ถ๊ฐํ๋ ๋ฉ์๋์
๋๋ค.
addToast(content: string, type: "success" | "warning" | "info" | "error") {
// ๋๋ฐ์ด์ค ํ์
์ ๋ฐ๋ผ ์ต๋ ํ ์คํธ ๋ฉ์์ง ๊ธธ์ด๋ฅผ ์ค์ ํฉ๋๋ค.
const maxLength = this.state.deviceType === "desktop" ? 5 : 0;
// ์ต๋ ๊ธธ์ด๋ฅผ ์ด๊ณผํ๋ฉด ๊ฐ์ฅ ์ค๋๋ ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
if (this.state.toasts.length > maxLength) this.state.toasts.shift();
// ๋๋คํ ID๋ฅผ ์์ฑํฉ๋๋ค.
const randIndex = Math.floor(Math.random() * 1000);
// ์๋ก์ด ํ ์คํธ ๋ฉ์์ง๋ฅผ ๋ฐฐ์ด์ ์ถ๊ฐํฉ๋๋ค.
this.state.toasts.push({
content: content,
id: randIndex,
visible: false,
type: type,
});
// ํ ์คํธ ๋ฉ์์ง๊ฐ ๋ณด์ด๋๋ก ์ค์ ํ๋ ํ์ด๋จธ๋ฅผ ์ค์ ํฉ๋๋ค.
setTimeout(() => {
toastStore.visibleToast(randIndex);
}, 1);
// ํ ์คํธ ๋ฉ์์ง๋ฅผ ์จ๊ธฐ๋ ํ์ด๋จธ๋ฅผ ์ค์ ํฉ๋๋ค.
setTimeout(() => {
toastStore.unVisibleToast(randIndex);
}, 4000);
// ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ญ์ ํ๋ ํ์ด๋จธ๋ฅผ ์ค์ ํฉ๋๋ค.
setTimeout(() => {
toastStore.deleteToast(randIndex);
}, 5000);
},
// ๋๋ฐ์ด์ค ํ์
์ ์ค์ ํ๋ ๋ฉ์๋์
๋๋ค.
setDiviceType(deviceType: "desktop" | "webview" | "mobile") {
this.state = {
...this.state,
deviceType,
};
},
// ID์ ํด๋นํ๋ ํ ์คํธ ๋ฉ์์ง๋ฅผ ๋ณด์ด๋๋ก ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฉ์๋์
๋๋ค.
visibleToast(id: number) {
const toasts = toJS(this.state.toasts);
const toastIndex = toasts.findIndex((toast) => {
if (toast?.id === id) {
return true;
}
});
this.state.toasts[toastIndex].visible = true;
this.state = {
...this.state,
toasts: [...this.state.toasts],
};
},
// ID์ ํด๋นํ๋ ํ ์คํธ ๋ฉ์์ง๋ฅผ ์จ๊ธฐ๋๋ก ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฉ์๋์
๋๋ค.
unVisibleToast(id: number) {
const toasts = toJS(this.state.toasts);
const toastIndex = toasts.findIndex((toast) => {
if (toast?.id === id) {
return true;
}
});
if (toastIndex !== -1) this.state.toasts[toastIndex].visible = false;
},
// ID์ ํด๋นํ๋ ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ญ์ ํ๋ ๋ฉ์๋์
๋๋ค.
deleteToast(id: number) {
const toastIndex = this.state.toasts.findIndex((toast) => {
if (toast?.id === id) {
return true;
}
});
if (toastIndex !== -1) this.state.toasts.splice(toastIndex, 1);
},
// ๊ณ์ฐ๋ ๊ฐ์ ์ ๊ณตํ๋ getter์
๋๋ค. ํ์ฌ ํ ์คํธ ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค.
//* computed value
get toasts(): ToastProps[] {
return toJS(this.state.toasts);
},
});
ํด๋น ์ฝ๋๋ MobX๋ฅผ ์ฌ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ ์คํธ ์๋ฆผ๋ค์ ๊ด๋ฆฌํ๋ Store์ ๋๋ค. observable ๊ฐ์ฒด๋ ์๋์ผ๋ก ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค UI๊ฐ ์ ๋ฐ์ดํธ๋๋๋ก ํฉ๋๋ค.
// ์ฌ์ฉ๋ฐฉ๋ฒ
// ํน์ ๋น์ง๋์ค ์ฝ๋์์ ์ฌ์ฉํ๋ ๋ฐฉ์
const { userStore, toastStore } = useStore();
try {
// ๋ก๊ทธ์ธ์ ์ํํ๋ ๋ก์ง
} catch (error) {
await logger.error(apiService.get.name, error);
toastStore.addToast("์ด๋ฉ์ผ ๋๋ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํด์ฃผ์ธ์", "error"); // UI๋ฅผ ์
๋ฐ์ดํธ ํฉ๋๋ค
router.push("/login");
}
// Toast.tsx
import React from "react";
import { observer } from "mobx-react";
import useStore from "@hooks/useStore";
import ToastPresenter from "./Toast.presenter";
import type { ToastComponentProps } from "./Toast.type";
const Toast = ({ isWebview }: ToastComponentProps) => {
const { toastStore } = useStore();
const deleteToast = (id: number) => {
toastStore.unVisibleToast(id);
setTimeout(() => {
toastStore.deleteToast(id);
}, 400);
};
return (
<ToastPresenter
toastList={toastStore.toasts} // toastStore์ ๋ฐฐ์ด์ ํ์ธํ์ฌ UI๋ฅผ ์
๋ฐ์ดํธ ํฉ๋๋ค.
deleteToast={deleteToast}
isWebview={isWebview}
/>
);
};
export default observer(Toast);
๋ค์๊ณผ ๊ฐ์ด Mobx๋ฅผ ํตํด์ Store๋ฅผ ์ ๋ฐ์ดํธ ํ๊ณ , Toast ์ปดํฌ๋ํธ๋ toastStore๊ฐ ์ ๋ฐ์ดํธ ๋จ์ ํ์ธํ๋ฉฐ UI๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.
์์ ๊ฐ์ด Mobx๋ Toast์ ์ํ๋ฅผ ์ง์์ ์ผ๋ก observable์ด๋ผ๋ ๊ด์ฐฐ ๊ฐ๋ฅํ ์ํ๋ฅผ ํตํด์ ์ปดํฌ๋ํธ ์ ๋ฐ์ดํธ๋ฅผ ํต์ ํ๋๊ฒ์ด ์ ์๊ฒ ์ต์ํ ๊ฐ๋ ์ด์์ต๋๋ค. ๋ํ ์ง๊ด์ ์ผ๋ก ์ํ๋ณ๊ฒฝ์ ์ผ์ผํค๊ธฐ ์ํด action์ ์ฌ์ฉํ๋๊ฒ ๋ํ ๋ง์์ ๋ค์์ต๋๋ค.
์์์๋ ์ ํ์๋ store.ts์์ ํ์ธํ ์ ์๋ฏ, state๋ฅผ ์ถ์ํํ์ฌ ๊ด๋ฆฌํ๋๊ฒ ๋ํ ๊ฐ์ฒด์งํฅ์ ์ผ๋ก ์ถ์ํ๊ฐ ์ ๋์ด ์๋๋ฏ ํ์ต๋๋ค.
Recoil
์ Facebook
์์ ๋ง๋ React
์ฉ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค. React์ Context API์ ์ ์ฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง๋ง, React ์ ํ๋ฆฌ์ผ์ด์
์์ ์ํ ๊ด๋ฆฌ๋ฅผ ๋ ํจ์จ์ ์ด๊ณ ์ ์ฐํ๊ฒ ๋ง๋ค๊ธฐ ์ํด ์ค๊ณ๋์์ต๋๋ค. ์ฌ๊ธฐ์๋ ๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ์ฃผ์ ํน์ง์ด ์์ต๋๋ค.
React ์ปดํฌ๋ํธ
๋ ์ด Atom์ ๊ตฌ๋
ํ ์ ์์ต๋๋ค. Atom ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ํด๋น Atom์ ๊ตฌ๋
ํ๋ ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง
๋ฉ๋๋ค.derived state
)์ ์ผ๋ถ๋ฅผ ๋ํ๋ด๋ ์์ ํจ์์
๋๋ค. Atom
์ด๋ ๋ค๋ฅธ Selector
์ ์ํ๋ฅผ ์
๋ ฅ์ผ๋ก ๋ฐ์ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๊ณ์ฐํฉ๋๋ค. Selector
๋ ์บ์๋ฅผ ์ฌ์ฉํ์ฌ ํจ์จ์ ์ธ ์ํ ํ์์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.Recoil
์ ๋น๋๊ธฐ ์ฟผ๋ฆฌ๋ฅผ ํฌํจํ ์ํ ๊ด๋ฆฌ๋ฅผ ์ฝ๊ฒ ํ ์ ์๊ฒ ํด์ค๋๋ค. ์ด๋ฅผ ํตํด ๋ฐ์ดํฐ ํ์นญ, ๋น๋๊ธฐ ๊ณ์ฐ ๋ฑ์ ์์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.useState
ํ
๊ณผ ์ ์ฌํ๊ฒ, Recoil์ ํจ์ํ ์
๋ฐ์ดํธ๋ฅผ ์ง์ํ์ฌ ์ด์ ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ์ํ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.Recoil
์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ ๊ณตํ์ฌ ์ํ ๋ณํ๋ฅผ ์๊ฐ์ ์ผ๋ก ์ถ์ ํ๊ณ ๋๋ฒ๊น
ํ๋ ๊ฒ์ ๋์์ค๋๋ค.Recoil
์ ์ฃผ์ ๋ชฉํ ์ค ํ๋๋React
์ ํ
API์ ์ ์ด์ฐ๋ฌ์ง๋๋ก ํ๋ ๊ฒ์
๋๋ค. ์ด๋ ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ์ํคํ
์ฒ์ ์์ฐ์ค๋ฝ๊ฒ ๋
น์๋ค์ด ์ํ ๊ด๋ฆฌ๋ฅผ ๋์ฑ ์ง๊ด์ ์ผ๋ก ๋ง๋ญ๋๋ค. ๋ํ, Atom
๊ณผ Selector
์ ๊ฐ๋
(Mobx์์์ state ์ ์ธ๋ฐฉ์๊ณผ ์ ์ฌ)์ ์ํ๋ฅผ ๋์ฑ ๋ชจ๋ํํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด, ํฌ๊ณ ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์
์์ ํนํ ์ ์ฉํฉ๋๋ค.
์์ผ๋ก ์งํํ๊ฒ ๋ recoil์ ๋ํด์๋ ๊น์ด ์๋ ํ์ต์ ์งํํด mobx์ ๋น๊ตํ๋ฉฐ ์ ์ญ์ํ๊ด๋ฆฌ ๋ฐฉ์์ ๋ํด ํ์ต์ด ํ์ํ ๋ฏ ํฉ๋๋ค.
์๋๋ ๊ฐ๋จํ๊ฒ ํ์ธํด๋ณธ recoil ์ฝ๋์ด๋ฉฐ mobx์์ ๊ฐ๋จํ ๋น๊ต๋ฅผ ํฌํจํฉ๋๋ค.
// ์ํ๊ด๋ฆฌ state ์ ์
// recoil -> ํ ์คํธ ์ํ๋ฅผ ์ ์ฅํ๋ atom์ ์ ์ํฉ๋๋ค.
export const toastState = atom({
key: 'toastState', // ๊ณ ์ ํ key
default: {
toasts: [],
deviceType: 'desktop',
}, // ๊ธฐ๋ณธ ์ํ
});
// mobx -> `state` ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํฉ๋๋ค. ์ฌ๊ธฐ์๋ `toasts` ๋ฐฐ์ด๊ณผ `deviceType` ๋ฌธ์์ด์ด ํฌํจ๋ฉ๋๋ค.
state: {
toasts: [],
deviceType: "desktop",
} as ToastState,
// ์ํ๊ด๋ฆฌ ๋ฉ์๋ ๊ด๋ฆฌ
// recoil -> selector๋ set ์์ฑ์ ํตํด ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
set: ({ set, get }, action) => {
const { toasts, deviceType } = get(toastState);
if (action.type === 'addToast') {
const maxLength = deviceType === 'desktop' ? 5 : 0;
if (toasts.length > maxLength) toasts.shift();
const randIndex = Math.floor(Math.random() * 1000);
toasts.push({
content: action.content,
id: randIndex,
visible: false,
type: action.toastType,
});
// ๋น๋๊ธฐ ์ก์
์ ์ฒ๋ฆฌํ๊ธฐ ์ํด setTimeout์ ์ฌ์ฉํฉ๋๋ค.
setTimeout(() => setVisibleToast(set, randIndex), 1);
setTimeout(() => setUnVisibleToast(set, randIndex), 4000);
setTimeout(() => deleteToast(set, randIndex), 5000);
}
set(toastState, { toasts, deviceType });
},
// mobx -> ๋งค์๋ ํ์์ผ๋ก addToast ํจ์๋ฅผ ์ ์ํฉ๋๋ค.
addToast(content: string, type: "success" | "warning" | "info" | "error") {
// ๋๋ฐ์ด์ค ํ์
์ ๋ฐ๋ผ ์ต๋ ํ ์คํธ ๋ฉ์์ง ๊ธธ์ด๋ฅผ ์ค์ ํฉ๋๋ค.
const maxLength = this.state.deviceType === "desktop" ? 5 : 0;
// ์ต๋ ๊ธธ์ด๋ฅผ ์ด๊ณผํ๋ฉด ๊ฐ์ฅ ์ค๋๋ ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
if (this.state.toasts.length > maxLength) this.state.toasts.shift();
// ๋๋คํ ID๋ฅผ ์์ฑํฉ๋๋ค.
const randIndex = Math.floor(Math.random() * 1000);
// ์๋ก์ด ํ ์คํธ ๋ฉ์์ง๋ฅผ ๋ฐฐ์ด์ ์ถ๊ฐํฉ๋๋ค.
this.state.toasts.push({
content: content,
id: randIndex,
visible: false,
type: type,
});
// ํ ์คํธ ๋ฉ์์ง๊ฐ ๋ณด์ด๋๋ก ์ค์ ํ๋ ํ์ด๋จธ๋ฅผ ์ค์ ํฉ๋๋ค.
setTimeout(() => {
toastStore.visibleToast(randIndex);
}, 1);
// ํ ์คํธ ๋ฉ์์ง๋ฅผ ์จ๊ธฐ๋ ํ์ด๋จธ๋ฅผ ์ค์ ํฉ๋๋ค.
setTimeout(() => {
toastStore.unVisibleToast(randIndex);
}, 4000);
// ํ ์คํธ ๋ฉ์์ง๋ฅผ ์ญ์ ํ๋ ํ์ด๋จธ๋ฅผ ์ค์ ํฉ๋๋ค.
setTimeout(() => {
toastStore.deleteToast(randIndex);
}, 5000);
},
์ฌ์ค โ์ recoil์ ์ฌ์ฉํด์ผ๋ง ํ์ดโ ๋ผ๋ ์ง๋ฌธ์๋ ์์ง ์๋ฒฝํ ๋๋ต์ ํ์ง ๋ชปํ ๊ฒ ๊ฐ์ต๋๋ค.
์ ๋ ์ง๊ธ๊น์ง mobx์ ์ต์ํ ์ฝ๋๋ฅผ ์์ฑํด์๊ณ , recoil ์ฝ๋๋ฅผ ์์ ๊ฐ์ด ์์ฑํด๋ณด์์ง๋ง, ์์ง mobx ์ฌ๊ณ ์์ ๋ฒ์ด๋์ง ๋ชปํ๋ฏ ํฉ๋๋ค.
์์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด๊ฐ๋ฉฐ recoil์ ํตํ ๊ด๋ฆฌ ๋ฐฉ์์ ์ฒ ํ์ ์ ์ดํดํ๊ณ ์ ๋ฆฌํ๋ฉฐ ์๋กญ๊ฒ ํ์ตํ๋๊ฒ ๋ํ ์๋ฏธ ์๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค.