-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3์กฐ #4
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "3\uC870"
3์กฐ #4
Conversation
<style> | ||
body { | ||
margin: 0; | ||
box-sizing: border-box; | ||
} | ||
</style> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
index.html์ ํตํด ๋ณ๋๋ก ๊ธ๋ก๋ฒ css๋ฅผ ์ ์ฉํ๋๊ฒ๋ณด๋ค
ํต์ผ์ฑ์๊ฒ styled component๋ฅผ ์ฌ์ฉํ๋๊ฒ์ด ๊ด๋ฆฌ์ธก๋ฉด์์ ๋ณด๋ค ์ข์ ์ฝ๋์
๋๋ค.
์ ์ฉ๊ณผ ๊ด๋ จ๋ ์์ธํ ๋ด์ฉ์ createGlobalStyle๋ฅผ ์ฐธ๊ณ ํ์๊ธธ ๋ฐ๋๋๋ค.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํด๋น ๋ด์ฉ์ /src/index.css
์๋ ๋์ผํ๊ฒ ์ ์ฉ๋ฉ๋๋ค.
|
||
const Layout = () => { | ||
const [showModal, setShowModal] = useState(false); | ||
const [inputdata, setInputData] = useState("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inputdata๋ฅผ camelCase๋ก ํต์ผ์์ผ์ฃผ์ธ์
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ๋ก์ ํธ ์งํํ์๋๋ผ ๊ณ ์ ๋ง์ผ์
จ์ต๋๋ค.
์ ๋ฐ์ ์ผ๋ก ์์ฌ์ ๋ ๋ด์ฉ์๋ํด ๋ช๊ฐ์ง ์ฝ๋ฉํธ ๋๋ฆฌ๋๋ก ํ๊ฒ ์ต๋๋ค.
- ์ปดํฌ๋ํธ ๋ถ๋ฆฌ๊ฐ ์ ์ด๋ฃจ์ด์ง์ง ์์๊ฒ ๊ฐ์ต๋๋ค. ํนํ styled-component๋ฅผ ์ฌ์ฉํ๋๋ฐ ์์ด ๊ณต์ฉ์ผ๋ก ์ฌ์ฉ๋๋ ๋ด์ฉ์ ๋ถ๋ฆฌํ๊ฑฐ๋ ์ญํ ๋ณ๋ก ๋ถ๋ฆฌ๋์ด ์์ง ์๋์ ์ ์ง์ค์ ์ผ๋ก ์์ ํด๋๊ฐ์๊ธธ ์ถ์ฒํฉ๋๋ค.
- vh, vw์ ๊ฐ์ ์์๋ ๋ฐ์ํ์ผ๋ก css๋ฅผ ์ ์ฉํ๋๋ฐ ์ด๋ ค์์ ์ค ์ ์์ต๋๋ค. calc๋ฅผ ์ฌ์ฉํ์ฌ ํ๋์ฝ๋ฉํ๊ฒ ๋๋ฉด ๊ตฌ์ฑ ์์์ ๋ณ๊ฒฝ์ฌํญ์ด ์๊ฒผ์ ๋ ์ด๋ฅผ ๋ฐ์ํ๊ธฐ ์ด๋ ค์์ง๋๋ค. root๋ฅผ ์ ์ธํ ์ปดํฌ๋ํธ์์๋ ์ฌ์ฉ์ ์ต๋ํ ์ง์ํ๊ณ ๋ค๋ฅธ ๋ฐฉํฅ์ผ๋ก ๊ตฌํํด๋ณด์๊ธธ ๋ฐ๋๋๋ค. (๋ค๋ง ์ ๋ง ํ์ํ ์ํฉ์์๋ ์ฌ์ฉํ๋๊ฒ์ด ๋ง์ต๋๋ค.)
- rem, px๋ฅผ ํผ์ฉํ์ฌ ์ฌ์ฉํ๊ธฐ๋ณด๋ค๋ ํ๊ฐ์ง๋ก ํต์ผ์ฑ ์๊ฒ ์์ ํ์๋๊ฒ์ ์ถ์ฒํฉ๋๋ค. rem์ ์ฌ์ฉํ๋ค๋ฉด font-size ์กฐ์ ์ ํตํด ๋ฐ์ํ ์นํ์ด์ง๋ฅผ ๊ตฌํํด๋ณด์๋ ๋ฐฉํฅ์ผ๋ก ์์ ํ์๋๊ฒ์ด ๋ณด๋ค ๋ชฉ์ ์ ๋ง์ต๋๋ค. ์ด์ ๊ด๋ จ๋ ๋ด์ฉ์ ์ฐพ์๋ณด์๋๊ฒ์ ์ถ์ฒํฉ๋๋ค.
<Routes> | ||
<Route path='/' element={<Layout />}> | ||
{/* <Route index element={<HomePage />} /> */} | ||
<Route index element={<MainPage />} /> | ||
<Route path=':movieId' element={<DetailPage />} /> | ||
<Route path='search/:searchId' element={<SearchPage />}/> | ||
<Route path='channel'> | ||
<Route index path=':channelId' element={<ChannelPage />} /> | ||
</Route> | ||
</Route> | ||
</Routes> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Routes๋ ๋ณ๋์ ํด๋๋ฅผ ๋ง๋ค์ด ๊ด๋ฆฌํ๋๊ฒ์ด ์ถํ์ ์ฐพ์๋ณด๊ธฐ ํธ๋ฆฌํ ๊ฒ๊ฐ์ต๋๋ค.
<svg className='hamburger' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'> | ||
<path d='M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z' /> | ||
</svg> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FontAwesomeIcon ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋์ผํ ์์ด์ฝ์ด ์๋๋ฐ๋ ๋ถ๊ตฌํ๊ณ ๋ณ๋๋ก svg๋ฅผ ์ฌ์ฉํ๋๊ฒ์ ๋ถํ์ํ ํ๋์ผ๋ก ์ธ์๋ฉ๋๋ค.
์ถ๊ฐ๋ก svg๋ฅผ ์ฌ์ฉํ๋๊ฒ ๋์๊ฒ์ ์๋๋ png๋ฅผ ์ฌ์ฉํ๋๊ฒ์ด ์ด๋ฏธ์ง๋ฅผ ๋ณ๋๋ก ๊ด๋ฆฌํ๋๋ฐ ์ด์ ์ ๊ฐ์ง ์ ์์ผ๋ฏ๋ก
png๋ก ์ ๊ณต ๋ฐ์์ ์์ ๊ฒฝ์ฐ svg๋ณด๋ค png๋ฅผ ์ฌ์ฉํ๋๊ฒ์ ์ถ์ฒํฉ๋๋ค.
const fetchData = async () => { | ||
await axios(requests.fetchPopularVideo).then(res => { | ||
const data = res.data.items; | ||
console.log(data); | ||
setYoutubeData(data); | ||
localStorage.setItem('PopularVideo', JSON.stringify(data)); | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
async-await ๊ตฌ๋ฌธ๊ณผ promise.then ๊ตฌ๋ฌธ์ ํผ์ฉํ๊ณ ์์ต๋๋ค.
const fetchData = async () => { | |
await axios(requests.fetchPopularVideo).then(res => { | |
const data = res.data.items; | |
console.log(data); | |
setYoutubeData(data); | |
localStorage.setItem('PopularVideo', JSON.stringify(data)); | |
}); | |
}; | |
const fetchData = async () => { | |
const {data} = await axios(requests.fetchPopularVideo) | |
setYoutubeData(data.items); | |
localStorage.setItem('PopularVideo', JSON.stringify(data.items)); | |
}; |
const Row = ({ items }) => { | ||
return ( | ||
<Container> | ||
{items.map((item) => ( | ||
<VideoItem key={item.id} data={item} /> | ||
))} | ||
</Container> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Row์ ๊ฐ์ด ๋๋ฌด ์ถ์ํ๋ ๋ค์ด๋ฐ์ผ๋ก ์ธํด Row Component๋ฅผ ํธ์ถํ๋ ํ์ผ์์ ์ง๊ด์ ์ผ๋ก ์ดํดํ๊ธฐ ์ด๋ ต์ต๋๋ค.
VideoItems๊ณผ ๊ฐ์ด ๋ณด๋ค ์ง๊ด์ ์ธ ์ด๋ฆ์ ์ฌ์ฉํ๋๊ฒ์ ์ถ์ฒํฉ๋๋ค.
frameborder: '0', | ||
allowfullscreen: 'allowfullscreen', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSX์์๋ HTML attribute๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ์ง ์๊ณ camelCase๋ก ์์ฑํด์ผํฉ๋๋ค.
๋ณด๋ค ์์ธํ ๋ด์ฉ์ ๊ณต์๋ฌธ์๋ฅผ ํ์ธํ์๊ธธ ๋ฐ๋๋๋ค.
const VideoItem = ({ data }) => { | ||
const movieId = data.id.videoId; | ||
|
||
return ( | ||
<Link to={`/${movieId}`} style={{ textDecoration: 'none' }}> | ||
<VideoContainer> | ||
<ImgWrapper> | ||
<ThumbnailImg | ||
src={data.snippet.thumbnails.maxres?.url || data.snippet.thumbnails.default?.url} | ||
alt={data.snippet.title} | ||
/> | ||
</ImgWrapper> | ||
|
||
<Thumbnail> | ||
<Title>{data.snippet.title}</Title> | ||
<Link to={`/channel/${data.snippet.channelId}`} style={{ textDecoration: 'none' }}> | ||
<Section> | ||
<span>{data.snippet.channelTitle}</span> | ||
<span>{moment(data.snippet.publishedAt).fromNow()}</span> | ||
</Section> | ||
</Link> | ||
</Thumbnail> | ||
</VideoContainer> | ||
</Link> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/components/MainComponents/Row.js์์ ๋์ผํ ์ฝ๋๋ฅผ ์ฌ์ฉ์ค์ ์์ต๋๋ค.
styled-component๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ์ฌ์ฌ์ฉ์ฑ์ ํฌ๊ธฐํ๋๊ฒ์ styled-component๋ฅผ ์ฌ์ฉํ๋ ์ด์ ์ ์๋ฐฐ๋๋ ํ๋์
๋๋ค.
์ฌ์ฌ์ฉ์ฑ ๊ฐ๋ฅํ๋๋ก props๋ฅผ ๋ฐ๋ component๋ฅผ ๋ง๋ค๊ฑฐ๋ styled-component๋ฅผ ์ฌ์ฉํ์ง ์๋๊ฒ์ ์ถ์ฒํฉ๋๋ค.
axios.get(`/search?part=snippet&maxResults=10&q=${searchId}`) | ||
.then(response => { | ||
console.log(response.data) | ||
const responsedata = response.data.items |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
responseData
์ ๊ฐ์ด camelCase๋ก ๋ณ๊ฒฝํ์๋๊ฒ์ ์ถ์ฒํฉ๋๋ค.
async function getChannelData() { | ||
try { | ||
const response = await axios(requests.fetchChannelData(channelId)); | ||
const cdata = response.data.items; | ||
setChannelData(cdata); | ||
const response2 = await axios(requests.fetchPlayLists(channelId)); | ||
const vdata = response2.data.items; | ||
setChannelVideo(vdata); | ||
} catch (error) { | ||
console.log(error); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
response, response2์ ๊ฐ์ด ๋ฌด์๋ฏธํ ๋ณ์๋ช ์ ๋ถ์ฌํ๋๊ฒ์ ์ข์ง ์์ต๋๋ค.
async function getChannelData() { | |
try { | |
const response = await axios(requests.fetchChannelData(channelId)); | |
const cdata = response.data.items; | |
setChannelData(cdata); | |
const response2 = await axios(requests.fetchPlayLists(channelId)); | |
const vdata = response2.data.items; | |
setChannelVideo(vdata); | |
} catch (error) { | |
console.log(error); | |
} | |
} | |
async function getChannelData() { | |
try { | |
const {data: channelData} = await axios(requests.fetchChannelData(channelId)); | |
setChannelData(channelData.items); | |
const {data: playLists} = await axios(requests.fetchPlayLists(channelId)); | |
setChannelVideo(playLists.items); | |
} catch (error) { | |
console.log(error); | |
} | |
} |
const Modal = styled.div` | ||
position: fixed; | ||
top: 75px; | ||
height: calc(100vh - 75px); | ||
width: 208.3px; | ||
background-color: white; | ||
transition: transform 300ms; | ||
z-index: 10; | ||
transform: ${({ showModal }) => | ||
showModal ? 'translateX(0)' : 'translateX(-100%)'}; | ||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๊ฐ์ ํ๋์ฝ๋ฉํ์ฌ css๋ฅผ ์ ์ฉํ๋ ๋ฐฉ์์ ๋ค์ง์ ๋ณ๊ฒฝ์ฌํญ์ ์ ์ฐํ๊ฒ ๋์ฒํ๊ธฐ ์ด๋ ค์ ์ต๋ํ ์ง์ํ๋๊ฒ์ด ์ข์ต๋๋ค.
์๋ฅผ ๋ค์ด width์ ๊ฐ์ ๊ฒฝ์ฐ ๊ณ ์ px๋ฅผ ์ง์ ํ๊ธฐ๋ณด๋ค padding๊ฐ์ ํตํด ์ฌ๋ฐฑ์ ์ฃผ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํ๋ฉด
๋์ผํ ๋์์ธ์ ๋ณด์ฌ์ฃผ์ง๋ง ์ ์ฐํ ์ฝ๋๊ฐ ๋ ๊ฒ์
๋๋ค.
const DetailPage = () => { | ||
let { movieId } = useParams(); | ||
console.log(movieId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์๋ ์ฝ๋๋ฅผ ๋ฃ์ผ์๊ณ ๋ก๊ทธ์ ๋์ค๋ ๋ฐ์ดํฐ๋ฅผ ํ์ธํ์๋ฉฐ ๋ํ ์ผ ์ปดํฌ๋ํธ๋ฅผ ๋งํฌ์ ํด๋ณด์๊ธธ ๋ฐ๋๋๋ค.
useEffect(() => {(async ()=> {
if (movieId) {
const response = await axios(requests.fetchDetailVideo(movieId));
console.log(response)
}
})();
},[movieId]);
์ด๋ ค์ ๋์ ๊ณผ ๊ด๋ จ๋ ์ฝ๋ฉํธ์ ๋๋ค.๐ป styled-components๋ฅผ ํ์ฉํ ๋ props ์ด์ฉํ์ฌ modal์ ์ด๊ณ ๋ซ๋ ๊ฒ๊ณผ ๋ฐ์ํ ๊ตฌํํ๋ ๋ถ๋ถ์ด ํ๋ค์์ต๋๋ค. useState์ ํจ๊ป ์ ์ฌ์ฉํ์ จ์ต๋๋ค. container ์์ฒด๋ฅผ modal๋ก ์ฌ์ฉํ๊ธฐ๋ณด๋ค modal๋ก ๋ํ๋ element๋ฅผ ํ๊ฐ ์ถ๊ฐํ์ฌ ๋ณ๋๋ก ๊ด๋ฆฌํ๋๊ฒ์ด ๋ณด๋ค ์ ์ฐํ๊ฒ ์ฌ์ฉ๊ธฐ ์ข์ต๋๋ค. (z-index๋ฑ์ ์ ์ฉ์ํค๋๋ฐ ์ด์ ) ๐ป Youtube api ํธ์ถ์ ์ ํ์ด ์์ด์ ๊ทธ ๋ถ๋ถ์ ํด๊ฒฐํ๊ธฐ ์ํด localstorage๋ก ๋์ฒํ๋ ๋ถ๋ถ์ด ๊น๋ค๋กญ๊ฒ ๋๊ปด์ก์ต๋๋ค. ๋ํ async await ๋น๋๊ธฐ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ด ์กฐ๊ธ ๊น๋ค๋ก์ ์ต๋๋ค. localStorage์ ๊ฒฝ์ฐ ๋ฌธ์์ด ๊ฐ๋ง ๋ค์ด๊ฐ์ ์์ต๋๋ค. ๊ทธ๋ ๊ธฐ์ JSON.parse๋ JSON.stringify๋ฑ์ด ํ์ํฉ๋๋ค. async-await ๊ตฌ๋ฌธ์ ์ฌ์ฉ์ด ์ด๋ ต๋ค๋ฉด Promise.then๊ณผ์ ์๊ด๊ด๊ณ์ ๋ํด ๊ณต๋ถํด๋ณด์๋๊ฒ์ ์ถ์ฒํฉ๋๋ค. ๐ป api๋ฅผ ํธ์ถํ๋ฉด์ ์ฌ๋ฌ๊ฐ์ง ์ค๋ฅ๋ค์ด ๋ฌ์๊ณ ์ด๋ฅผ ํด๊ฒฐํ๋ ๊ฒ์ด ์ด๋ ค์ ์ต๋๋ค. api๋ฅผ ํธ์ถํ์๋ฉฐ ๊ฒช์ผ์ ์ค๋ฅ๋ค์ด ์ด๋ค๊ฒ์ธ์ง ๋ชฐ๋ผ ์์ธํ ํผ๋๋ฐฑ์ ๋๋ฆฌ๊ธฐ ์ด๋ ต์ต๋๋ค. useState์ useEffect๋ฅผ ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ react์ life-cycle๊ณผ ๊ด๋ จ๋ ๋ด์ฉ์ ์ดํดํ๊ณ ์๋๊ฒ์ด ์ค์ํฉ๋๋ค. |
๐ ํ๋ก์ ํธ ์ ๋ชฉ ๐
YouTube๐ ํ๋ก์ ํธ ์๊ฐ ๐
Google์์ ์ ๊ณตํ๋ YouTube API๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ YouTube ์ฑ์ ๋ง๋ ๋ค.๐ค ํ์ ์๊ฐ ๐ค
๐ ๊ธฐ์ ์คํ ๐
โฐ ๊ณผ์ ์งํ ๊ธฐ๊ฐ โฐ
2023.3.10 ~ 2023.3.16
โ ์์ ์์ญ ๋ฐ ๊ตฌํ ๊ธฐ๋ฅ ์ค๋ช โ
โพ ๊น์์ด
๐ป ํํ์ด์ง header์ sidebar, sidebar modal ๊ตฌํ
๐ป ํํ์ด์ง ๋น๋์ค ์นด๋ ์คํ์ผ๋ง
โพ ์ฅ๊ฑด์ฐ
๐ป Mainpage, Channelpage, MainComponent ๊ตฌํ
๐ป ํํ์ด์ง ๋น๋์ค ์ด๋ฏธ์ง, ๋น๋์ค ํ์ดํ, ์ฑ๋ ์ด๋ฆ, ๋ ์ง ๋ฑ์ ๋ฐ์ดํฐ๋ฅผ ๋ ๋๋ง
๐ป ChannelPage๋ ๋ฉ์ธํ์ด์ง์ ์ฑ๋์ด๋ฆ์ ๋๋ฅด๋ฉด ์ด๋ํ๋๋ก ๊ตฌํํ์๊ณ
useEffect๋ก ํด๋น channelId๋ฅผ ํ์ฉํ์ฌ(useParams๋ก ๊ฐ์ ธ์ด)
axios๋ก api ํธ์ถ์ ํด์ ์ฑ๋ ๊ด๋ จ ๋น๋์ค๊ฐ 10๊ฐ ๋ฐ์์ง๋๋ก ๊ตฌํ
โพ ์ต์ฐ์ฑ
๐ป SearchPage, SearchMain ๊ตฌํ
๐ป useState์ useEffect๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์์ด๋ฅผ ์ ์ฅ. ๊ฒ์์ด๋ useParams๋ฅผ ์ฌ์ฉํ์ฌ URL์์ ๊ฐ์ ธ์ค๊ณ ,
๊ทธ ๊ฒฐ๊ณผ๋ฅผ SearchMain ์ปดํฌ๋ํธ์ ์ ๋ฌํ์ฌ ํ๋ฉด์ ์ถ๋ ฅํ๋ ๊ฒ์ ๊ตฌํ
โพ ์ดํ์
๐ป DetailPage ๊ตฌํ ์ ์ค๋ฅ
์ด๋ ค์ ๋ ์ ๐น
๐ป styled-components๋ฅผ ํ์ฉํ ๋ props ์ด์ฉํ์ฌ modal์ ์ด๊ณ ๋ซ๋ ๊ฒ๊ณผ ๋ฐ์ํ ๊ตฌํํ๋ ๋ถ๋ถ์ด ํ๋ค์์ต๋๋ค.
๐ป Youtube api ํธ์ถ์ ์ ํ์ด ์์ด์ ๊ทธ ๋ถ๋ถ์ ํด๊ฒฐํ๊ธฐ ์ํด localstorage๋ก ๋์ฒํ๋ ๋ถ๋ถ์ด ๊น๋ค๋กญ๊ฒ ๋๊ปด์ก์ต๋๋ค. ๋ํ async await ๋น๋๊ธฐ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ด ์กฐ๊ธ ๊น๋ค๋ก์ ์ต๋๋ค.
๐ป api๋ฅผ ํธ์ถํ๋ฉด์ ์ฌ๋ฌ๊ฐ์ง ์ค๋ฅ๋ค์ด ๋ฌ์๊ณ ์ด๋ฅผ ํด๊ฒฐํ๋ ๊ฒ์ด ์ด๋ ค์ ์ต๋๋ค.
๋ api์ ๋ถํ์ํ ํธ์ถ์ ์ต์ํํ๋ ๊ฒ์ด๋ ๋ด๊ฐ ์ํ๋ ๊ฒ์์ด๋ง ๊ฐ์ ธ์ค๊ฒ ํ๋ ๊ฒ์ด ์๊ฐ๋ณด๋ค ๊น๋ค๋ก์ ์ต๋๋ค. useState ๋ useEffect ๊ฐ์ Hook์ ์ ์ฌ์ ์์ ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ด๋ ค์ ์ต๋๋ค.