-
[React] ์ค์ต - React Twittler State & PropscodeStates front-end/React 2023. 1. 30. 20:30๋ฐ์ํ
๐ react twittler SPA
๐ ํ์ต ๋ชฉํ
React Router ์ค์น
- React Router๋ฅผ npm์ผ๋ก ์ค์นํด์ผ ํฉ๋๋ค.
์์ธ ์ปดํฌ๋ํธ ๊ตฌํํ๊ธฐ
์ํ๋ ๋ ์ด์์์ ๋ผ์ ๋ฃ์ ์ ์๊ฒ ์์ธ ์ปดํฌ๋ํธ๋ฅผ ๋จผ์ ๊ตฌํํฉ๋๋ค.
Sidebar ์ปดํฌ๋ํธ (Sidebar.js)
- Sidebar ์ปดํฌ๋ํธ๋ ์ด๋ฏธ ๊ตฌํ๋์ด ์์ต๋๋ค. Sidebar.js ํ์ผ์์ ์ง์ ํ์ธํ์ธ์.
Footer ์ปดํฌ๋ํธ (Footer.js)
- Footer ์ปดํฌ๋ํธ์ ํ์ ์๋ฆฌ๋จผํธ๋ก ์๋ฉํฑ ์๋ฆฌ๋จผํธ <footer>๊ฐ ์์ด์ผ ํฉ๋๋ค.
Tweet ์ปดํฌ๋ํธ (Tweet.js)
- ๊ฐ ํธ์์ ๊ผญ ํ์ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์์ด์ผ ํฉ๋๋ค. (ํ๋กํ ์ฌ์ง, ์ ์ ์ด๋ฆ, ํธ์ ์์ฑ ์ผ์, ํธ์ ๋ฉ์์ง)
- ํ๋กํ ์ฌ์ง์ ๋ฃ๊ธฐ ์ํด <img> ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ , src ์์ฑ์ ์ ๋ฌ๋ฐ์ props์ ์ฌ์ง ์ ๋ณด๊ฐ ๋ค์ด๊ฐ ์๋์ง ํ์ธํด ์ฃผ์ธ์.
- ์ ์ ์ด๋ฆ์ ๋ฃ๊ธฐ ์ํด <span> ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ , class ์ด๋ฆ์ tweet__username๋ก ์ง์ ํ์ธ์.
- ํธ์ ์์ฑ ์ผ์๋ฅผ ๋ฃ๊ธฐ ์ํด <span>์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ , class ์ด๋ฆ์ tweet__createdAt์ผ๋ก ์ง์ ํ์ธ์.
- ๋ ์ง ํ์์ yyyy. mm. dd. ์ด์ด์ผ ํ๊ณ , parsedDate ๋ณ์๋ฅผ ์ด์ฉํ์ธ์.
- ํธ์ ๋ฉ์์ง๋ฅผ ๋ฃ๊ธฐ ์ํด <div> ์๋ฆฌ๋จผํธ๊ฐ ์์ฑ๋์ด ์์ต๋๋ค. class ์ด๋ฆ์ tweet__message๋ก ์ง์ ๋์ด ์๋์ง ํ์ธํด ์ฃผ์ธ์.
- ํธ์ ๋ฉ์์ง๋ฅผ div.tweet__message์ textContent๋ก ๋ฃ์ต๋๋ค.
- dummyTweets๊ฐ ์๋ ๋ค๋ฅธ ๋ฐ์ดํฐ๊ฐ props๋ก ์ ๋ฌ๋์ด๋ ํธ์ ์ ๋ณด๋ฅผ ์ ํํ๊ฒ ํ์ํด์ผ ํฉ๋๋ค.
ํ์ด์ง ์ปดํฌ๋ํธ ๊ตฌํํ๊ธฐ
ํ์ด์ง๋ฅผ ๊ตฌ์ฑํ๋ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค.
์ด๋ฒ ๊ณผ์ ์ ํ์ฌ ์ ์ ๋ parkhacker์ด๊ธฐ ๋๋ฌธ์, Twittler์ MyPage์์ parkhacker์ ํธ์๋ง ๋ณด์ฌ์ผ ํ๋ ์๊ตฌ์ฌํญ์ ๋ง์กฑํด์ผ ํฉ๋๋ค.
About ์ปดํฌ๋ํธ (About.js)
- About ์ปดํฌ๋ํธ์ ์์ ์ปดํฌ๋ํธ๋ก Footer ์ปดํฌ๋ํธ๊ฐ ์๋์ง ํ์ธํด ์ฃผ์๊ณ , ์๋ค๋ฉด ์ฐ๊ฒฐํด ์ฃผ์ธ์.
MyPage ์ปดํฌ๋ํธ (MyPage.js)
- ์ฃผ์ด์ง ํธ์ ๋ชฉ๋ก(dummyTweets)์ค ํ์ฌ ์ ์ ์ธ parkhacker์ ํธ์๋ง ๋ณด์ฌ์ผ ํฉ๋๋ค.
- const ๋ณ์๋ก ์ ์ธ๋ filteredTweets์ ํ์ฉํด parkhacker์ ํธ์์ด ๋ณด์ด๋๋ก ๊ตฌํํด ์ฃผ์ธ์.
- MyPage ์ปดํฌ๋ํธ์ ์์์ธ Tweet ์ปดํฌ๋ํธ์ props๋ก ๊ฐ ํธ์์ ์ ๋ณด(dummyTweets์ ์์)๊ฐ ์ ๋ฌ๋์ด์ผ ํฉ๋๋ค.
- MyPage ์ปดํฌ๋ํธ์ ์์ ์ปดํฌ๋ํธ๋ก Footer ์ปดํฌ๋ํธ๊ฐ ์์ด์ผ ํฉ๋๋ค.
MyPage๋ dummyTweets๋ก ๊ตฌํ๋ ์ปดํฌ๋ํธ์ ๋๋ค.
State&Props๋ก ๊ตฌ์ฑํ ์ปดํฌ๋ํธ์ ๋น๊ตํ์ฌ ์ State&Props๋ฅผ ์ฌ์ฉํด์ผํ๋์ง ์ดํดํด๋ณด์ธ์.Tweets ์ปดํฌ๋ํธ (Tweets.js)
- ํ๋์ ํธ์์ด ์๋๋ผ, ์ฃผ์ด์ง ํธ์(dummyTweets) ๊ฐ์์ ๋ง๊ฒ ๋ณด์ฌ์ค์ผ ํฉ๋๋ค.
- Tweets ์ปดํฌ๋ํธ์ ์์ ์ปดํฌ๋ํธ๋ก Footer ์ปดํฌ๋ํธ๊ฐ ์์ด์ผ ํฉ๋๋ค.
React Router ์ ์ฉํ๊ธฐ
์ด์ React Twittler SPA ๊ณผ์ ์์ ๋ฐฐ์ด ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์๋ ๊ธฐ์ ์๊ตฌ์ฌํญ์ ๊ตฌํํฉ๋๋ค.
React Router ์ปดํฌ๋ํธ ์ ์ฉ
- Route path๊ฐ "/" ์ธ Tweets ์ปดํฌ๋ํธ๊ฐ ์์ด์ผ ํฉ๋๋ค.
- Route path๊ฐ "/about" ์ธ About ์ปดํฌ๋ํธ๊ฐ ์์ด์ผ ํฉ๋๋ค.
- Route path๊ฐ "/mypage" ์ธ MyPage ์ปดํฌ๋ํธ๊ฐ ์์ด์ผ ํฉ๋๋ค.
- React Router์ Link ์ปดํฌ๋ํธ๊ฐ 3๊ฐ ์์ด์ผ ํฉ๋๋ค.
- Tweets ์์ด์ฝ์ Link ์ปดํฌ๋ํธ๋ "/" ๋ก ์ฐ๊ฒฐ๋์ด์ผ ํฉ๋๋ค.
- About ์์ด์ฝ์ Link ์ปดํฌ๋ํธ๋ "/about" ๋ก ์ฐ๊ฒฐ๋์ด์ผ ํฉ๋๋ค.
- MyPage ์์ด์ฝ์ Link ์ปดํฌ๋ํธ๋ "/mypage" ๋ก ์ฐ๊ฒฐ๋์ด์ผ ํฉ๋๋ค.
React Router๋ก SPA ๊ตฌํํ๊ธฐ
- ์ฒ์ ์ ์ ์, URL path๊ฐ / ์ด์ด์ผ ํฉ๋๋ค.
- About ๋ฉ๋ด๋ฅผ ๋๋ฅด๋ฉด URL path๊ฐ /about์ผ๋ก ๋ผ์ฐํธ ๋์ด์ผ ํฉ๋๋ค.
- Mypage ๋ฉ๋ด๋ฅผ ๋๋ฅด๋ฉด URL path๊ฐ /mypage๋ก ๋ผ์ฐํธ ๋์ด์ผ ํฉ๋๋ค.
State, Props ํ์ฉ ํธ์ ์ ์ก Form ๋ง๋ค๊ธฐ
์ด๋ฒ ๊ณผ์ ์ ํต์ฌ์ ๋๋ค. Tweets ์ปดํฌ๋ํธ์์ ์ด๋ค ๋ฐ์ดํฐ๊ฐ state๊ฐ ๋์ด์ผ ํ๊ณ , ์ด๋ค ๋ฐ์ดํฐ๋ฅผ props๋ก ํ์ ์ปดํฌ๋ํธ์ ์ ๋ฌํด์ผ ํ ์ง ๊ณ ๋ฏผํ๊ณ ์๋ ๊ธฐ์ ์๊ตฌ์ฌํญ์ ๊ตฌํํฉ๋๋ค. State, Props ๋ ์จ๊ณผ ์ค์ต์ด ๋์์ด ๋ ๊ฒ๋๋ค. ๐
Tweets.js ํธ์ ์ ์ก Form ํ ์คํธ
- ์ ์ ์ด๋ฆ์ ์์ฑํ ์ ์๋ input ์๋ฆฌ๋จผํธ๊ฐ ์์ด์ผ ํฉ๋๋ค. (className : "tweetForm__input--username")
- <input>์ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ onChange ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ๋ถ๋ ค์ผ ํฉ๋๋ค.
- ํธ์์ ์์ฑํ ์ ์๋ textarea ์๋ฆฌ๋จผํธ๊ฐ ์์ด์ผ ํฉ๋๋ค. (className : "tweetForm__input--message")
- <textarea>์ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ onChange ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ๋ถ๋ ค์ผ ํฉ๋๋ค.
- ํธ์์ ์ ์กํ ์ ์๋ button ์๋ฆฌ๋จผํธ๊ฐ ์์ด์ผ ํฉ๋๋ค. (className : "tweetForm__submitButton")
- <button>์ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ onClick ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ๋ถ๋ ค์ผ ํฉ๋๋ค.
- ์ ์ ์ด๋ฆ๊ณผ ํธ์์ ์์ฑํ๊ณ , ํธ์ ๋ฒํผ์ ๋๋ฅด๋ฉด ์๋ก์ด ํธ์์ด ์ถ๊ฐ๋์ด์ผ ํฉ๋๋ค.
- ๊ธฐ์กด dummyTweets๋ฅผ ๋ชจ๋ ๋ณด์ฌ์ค์ผ ํฉ๋๋ค.
- ์๋ก ์ถ๊ฐ๋ ํธ์์ ํฌํจํ์ฌ ๋ณด์ฌ์ค์ผ ํฉ๋๋ค.
- ์๋ก ์ถ๊ฐ๋ ํธ์์ด ์ต์๋จ์ ์์นํ์ฌ์ผ ํฉ๋๋ค.
๋ณธ ์ค์ต์ ์ฝ๋์คํ ์ด์ธ ์์ ์ํํ์์ต๋๋ค.
์ฝ๋ฉ๋ถํธ์บ ํ | ์ฝ๋์คํ ์ด์ธ - ๋น์ ๊ณต์๋ ๊ฐ๋ฐ์๊ฐ ๋ ์ ์์ต๋๋ค
์ฝ๋ฉ๋ถํธ์บ ํ๋ฅผ ์ฐพ๋๋ค๋ฉด? ๊ฐ๋ฐ์๋ก ์ปค๋ฆฌ์ด ์ ํ์ ์ํ ์ฑ ์์๋ ์ฝ๋ฉ ๊ต์ก ๊ธฐ๊ด! ์๋น์ค ๊ธฐํ์, ๊ทธ๋ก์ค ๋ง์ผํฐ, ๋ฐ์ดํฐ ์ฌ์ด์ธํฐ์คํธ ๋ฑ ๋ค์ํ ์ ๋ฌธ ์ปค๋ฆฌ์ด์ ๋์ ํ์ธ์. ์ทจ์ ์ฑ๊ณต์ ํ๊ธฐ
www.codestates.com
๋๋ถ๋ถ์ ๋ด์ฉ์ ์๋ ์ค์ต๊ณผ ๋น์ทํฉ๋๋ค
https://hwantech.tistory.com/181
[React] ์ค์ต - react twittler SPA
๐ react twittler SPA ๐ ํ์ต ๋ชฉํ React Router ์ค์น react-router-dom ์ npm์ผ๋ก ์ค์นํด์ผ ํฉ๋๋ค. ์์ธ ์ปดํฌ๋ํธ ๊ตฌํํ๊ธฐ App ๋ฃจํธ ์ปดํฌ๋ํธ(App.js) import ๋ฅผ ์ด์ฉํ์ฌ Tweets, MyPage, About ์ปดํฌ๋ํธ๋ฅผ ๋ถ๋ฌ์ต
hwantech.tistory.com
+ ์ถ๊ฐ๋ด์ฉ๋ง ๋ธ๋ก๊น
component/Tweet.js
import React from 'react'; import './Tweet.css'; // dummyTweets๊ฐ ์๋ ๋ค๋ฅธ ๋ฐ์ดํฐ๊ฐ props๋ก ์ ๋ฌ๋์ด๋ ํธ์ ์ ๋ณด๋ฅผ ์ ํํ๊ฒ ํ์ const Tweet = ({ tweet }) => { //๋ ์ง ํ์์ yyyy. mm. dd. ์ด์ด์ผ ํ๊ณ , parsedDate ๋ณ์๋ฅผ ์ด์ฉ const parsedDate = new Date(tweet.createdAt).toLocaleDateString('ko-kr'); return ( <li className="tweet" id={tweet.id}> <div className="tweet__profile"> <img src={tweet.picture} /> </div> <div className="tweet__content"> <div className="tweet__userInfo"> <div className="tweet__userInfo--wrapper"> {/* TODO : ์ ์ ธ ์ด๋ฆ์ด ์์ด์ผ ํฉ๋๋ค. */} {/* TODO : ํธ์ ์์ฑ ์ผ์๊ฐ ์์ด์ผ ํฉ๋๋ค. parsedDate๋ฅผ ์ด์ฉํ์ธ์. */} //์ ์ ์ด๋ฆ์ ๋ฃ๊ธฐ ์ํด <span> ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ , class ์ด๋ฆ์ tweet__username๋ก ์ง์ ํ์ธ์. <span className="tweet__username">{tweet.username}</span> //ํธ์ ์์ฑ ์ผ์๋ฅผ ๋ฃ๊ธฐ ์ํด <span>์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ๊ณ , class ์ด๋ฆ์ tweet__createdAt์ผ๋ก ์ง์ <span className="tweet__createdAt">{parsedDate}</span> </div> </div> <div className="tweet__message"> {/* TODO : ํธ์ ๋ฉ์ธ์ง๊ฐ ์์ด์ผ ํฉ๋๋ค. */} // ํธ์ ๋ฉ์์ง๋ฅผ div.tweet__message์ textContent๋ก ๋ฃ์ต๋๋ค. {tweet.content} </div> </div> </li> ); }; export default Tweet;
Pages/tweets.js
// TODO : useState๋ฅผ react๋ก ๋ถํฐ import ํฉ๋๋ค. import React, { useState } from 'react'; import Footer from '../Footer'; import Tweet from '../Components/Tweet'; import './Tweets.css'; import dummyTweets from '../static/dummyData'; const Tweets = () => { // TODO : ์๋ก ํธ์์ ์์ฑํ๊ณ ์ ์กํ ์ ์๊ฒ useState๋ฅผ ์ ์ ํ ํ์ฉํ์ธ์. const getRandomNumber = (min, max) => { return parseInt(Math.random() * (Number(max) - Number(min) + 2)); }; const [user, setUser] = useState('parkhacker'); const [msg, setMsg] = useState(''); const [tweets, setTweets] = useState(dummyTweets); const handleButtonClick = (event) => { const tweet = { id: dummyTweets.length, username: user, picture: `https://randomuser.me/api/portraits/women/${getRandomNumber( 1, 98 )}.jpg`, content :msg, createdAt : new Date().toLocaleDateString('ko-kr'), updatedAt : new Date().toLocaleDateString('ko-kr'), }; // TODO : Tweet button ์๋ฆฌ๋จผํธ ํด๋ฆญ์ ์๋ํ๋ ํจ์๋ฅผ ์์ฑํ์ธ์. // ํธ์ ์ ์ก์ด ๊ฐ๋ฅํ๊ฒ ์์ฑํด์ผ ํฉ๋๋ค. setTweets([tweet, ...tweets]) }; const handleChangeUser = (event) => { setUser(event.target.value) // TODO : Tweet input ์๋ฆฌ๋จผํธ์ ์ ๋ ฅ ์ ์๋ํ๋ ํจ์๋ฅผ ์์ฑํ์ธ์. }; const handleChangeMsg = (event) => { setMsg(event.target.value) // TODO : Tweet textarea ์๋ฆฌ๋จผํธ์ ์ ๋ ฅ ์ ์๋ํ๋ ํจ์๋ฅผ ์์ฑํ์ธ์. }; return ( <React.Fragment> <div className="tweetForm__container"> <div className="tweetForm__wrapper"> <div className="tweetForm__profile"> <img src="https://randomuser.me/api/portraits/men/98.jpg" /> </div> <div className="tweetForm__inputContainer"> <div className="tweetForm__inputWrapper"> <div className="tweetForm__input"> <input type="text" defaultValue="parkhacker" value={user} placeholder="your username here.." className="tweetForm__input--username" onChange={handleChangeUser} ></input> TODO : ํธ์์ ์์ฑํ ์ ์๋ textarea ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ์ธ์. <textarea defaultValue={""} placeholder="your message here.." className="tweetForm__input--message" onChange={handleChangeMsg} value={msg} ></textarea></div> <div className="tweetForm__count" role="status"> <span className="tweetForm__count__text"> {/* TODO : ํธ์ ์ด ๊ฐ์๋ฅผ ๋ณด์ฌ์ค ์ ์๋ Counter๋ฅผ ์์ฑํ์ธ์. */} {'total: ' + tweets.length} </span> </div> </div> <div className="tweetForm__submit"> <div className="tweetForm__submitIcon"></div> {/* TODO : ์์ฑํ ํธ์์ ์ ์กํ ์ ์๋ button ์๋ฆฌ๋จผํธ๋ฅผ ์์ฑํ์ธ์. */} <button className="tweetForm__submitButton" onClick={handleButtonClick}>Tweet</button> </div> </div> </div> </div> <div className="tweet__selectUser"></div> <ul className="tweets"> {/* TODO : ํ๋์ ํธ์์ด ์๋๋ผ, ์ฃผ์ด์ง ํธ์ ๋ชฉ๋ก(dummyTweets) ๊ฐฏ์์ ๋ง๊ฒ ๋ณด์ฌ์ค์ผ ํฉ๋๋ค. */} {tweets.map((el) => { return ( <Tweet tweet={el} /> )})} </ul> <Footer /> </React.Fragment> ); }; export default Tweets;
Pages/MyPage.js
import React from 'react'; import Footer from '../Footer'; import Tweet from '../Components/Tweet'; import './MyPage.css'; import dummyTweets from '../static/dummyData'; const MyPage = () => { const filteredTweets = dummyTweets.filter((tweet) => { return tweet.username === 'parkhacker'; }) // TODO : ์ฃผ์ด์ง ํธ์ ๋ชฉ๋ก(dummyTweets)์ค ํ์ฌ ์ ์ ธ์ธ parkhacker์ ํธ์๋ง ๋ณด์ฌ์ค์ผ ํฉ๋๋ค. return ( <section className="myInfo"> <div className="myInfo__container"> <div className="myInfo__wrapper"> <div className="myInfo__profile"> <img src={filteredTweets[0].picture} /> </div> <div className="myInfo__detail"> <p className="myInfo__detailName"> {filteredTweets[0].username} Profile </p> <p>28 ํ๋ก์ 100 ํ๋ก์</p> </div> </div> </div> <ul className="tweets__mypage"> <Tweet tweet={filteredTweets[0]}/> {/* TODO : ์ฃผ์ด์ง ํธ์ ๋ชฉ๋ก(dummyTweets)์ค ํ์ฌ ์ ์ ธ์ธ parkhacker์ ํธ์๋ง ๋ณด์ฌ์ค์ผ ํฉ๋๋ค. */} </ul> <Footer /> </section> ); }; export default MyPage;
๋ฐ์ํ'codeStates front-end > React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[React] Effect Hook (0) 2023.02.02 [React] ๋ฐ์ดํฐ ํ๋ฆ (0) 2023.02.02 [React] ์ด๋ฒคํธ ์ฒ๋ฆฌ & Controlled Component & React ๋ฐ์ดํฐ ํ๋ฆ (0) 2023.01.26 [React] State & Props & ๋ ๋๋ง ์ ์ (0) 2023.01.25 [React] ์ค์ต - react twittler SPA (0) 2023.01.25