💻/[과제]

[과제] React Twittler State & Props

Mia_ 2022. 12. 4. 19:47

File Structure

/
├── /React Twittler State Props
│   ├── README.md
│   ├── /public                        # create-react-app이 만들어낸 파일, yarn/npm start로 실행할 시에 쓰입니다
│   └── /src                           # React 컴포넌트가 들어가는 폴더
│        ├── static                    # dummyData가 들어가는 폴더
│        │    └── dummyData.js
│        ├── Pages                     # 페이지를 표시하는 컴포넌트가 들어가는 폴더
│        │    ├── About.css
│        │    ├── About.js
│        │    ├── Mypage.css
│        │    ├── Mypage.js
│        │    ├── Tweets.css
│        │    └── Tweets.js
│        ├── Components                # 단일 컴포넌트가 들어가는 폴더
│        │    ├── Tweet.css
│        │    └── Tweet.js
│        ├── App.css
│        ├── App.js
│        ├── Footer.js
│        ├── index.js
│        └── Sidebar.js
├  package.json
└ .gitignore

 

App.js
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// TODO : React Router DOM을 설치 후, import 구문을 이용하여 BrowserRouter, Routes, Route 컴포넌트를 불러옵니다. - done!

import Sidebar from './Sidebar';
import Tweets from './Pages/Tweets';
// TODO : MyPage, About 컴포넌트를 import 합니다. -done!
import MyPage from './Pages/MyPage';
import About from './Pages/About';

import './App.css';
import './global-style.css';

const App = (props) => {
  return (
    <BrowserRouter>
    <div className="App">
      <main>
        <Sidebar /> {/*사이드 바는 모든 페이지에 있으니까*/}
        <section className="features">
          {/* TODO : 유어클래스를 참고해서, 테스트 케이스를 통과하세요. - done!
            TODO : React Router DOM 설치 후 BrowserRouter, Routes, Route의 주석을 해제하고 
            Routes, Route 컴포넌트를 적절하게 작성합니다. - done!*/}
          <Routes>
            <Route path='/' element={<Tweets />} /> {/* 요소에 경로 지정*/}
            <Route path='/about' element={<About />} />
            <Route path='/mypage' element={<MyPage />} />
          </Routes>
        </section>
      </main>
    </div>
    </BrowserRouter>
  );
};

// ! 아래 코드는 수정하지 않습니다.
export default App;

 

 

Footer.js
import React from 'react';

const Footer = () => {
  return <footer><div></div></footer>;
};
// TODO : Footer 함수 컴포넌트를 작성합니다. 시멘틱 요소 footer가 포함되어야 합니다. - done!

export default Footer;

 

 

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import dummyTweets from './static/dummyData';

ReactDOM.render(
  <App dummyTweets={dummyTweets} />,
  document.getElementById('root')
);
//원래 ReactDOM 렌더 단계에서 <BrowserRouter>를 넣어서 뿌리는게 국룰임

 

 

 

Sidebar.js
import React from 'react';
// TODO : React Router DOM의 Link 컴포넌트를 import 합니다. - done!
import { Link } from 'react-router-dom';

const Sidebar = () => {
  return (
    <section className="sidebar">
      {/* TODO : Link 컴포넌트를 작성하고, to 속성을 이용하여 경로(path)를 연결합니다. - done! */}
      <Link to="/"><i className="far fa-comment-dots"></i></Link>
      <Link to="/about"><i className="far fa-question-circle"></i></Link>
      <Link to="/mypage"><i className="far fa-user"></i></Link>
    </section>
  );
};

export default Sidebar;

 

 

Components
Tweet.js

: 단일 컴포넌트가 들어있는 폴더; Components

import React from 'react';
import './Tweet.css';

const Tweet = ({ tweet }) => {
  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 : 유져 이름이 있어야 합니다. - done! */}
            <span className='tweet__username'>{tweet.username}</span>          
            {/* TODO : 트윗 생성 일자가 있어야 합니다. parsedDate를 이용하세요. - done!  */}
            <span className='tweet__createdAt'>{parsedDate}</span>
          </div>
        </div>
          {/* TODO : 트윗 메세지가 있어야 합니다. - done! */}
          <div className='tweet__message'>{tweet.content}</div>
      </div>
    </li>
  );

};

export default Tweet;

- props를 이용하여 정의된 값과 속성을 전달

- 함수에 인자를 전달하듯 React 컴포넌트에 props를 전달하면 되고, 이 props가 필요한 모든 데이터를 가지고 오게 됨

 

 

Pages
Tweets.js

: 페이지를 표시하는 컴포넌트가 들어가는 폴더;Pages

 

- [state 저장 변수, state 갱신 함수] = useState(상태 초기값);

 

- 서로 다른 주소값을 가져야 다른 자료형으로 인지 ---> State가 다를 때 리렌더링을 함 

- 참조 자료형이 다른 자료값을 가지고 있어야 리렌더링이 된다는 것이 포인트!

 

- 1. 깊은 복사 (내용 통으로 다 복사하는 것, 주소만 복사(얕은 복사)하는게 아니라 값을 아예 다 복사)

- 2. Spread 문법 (깊은 복사 개념)

- 얕은 복사는 같은 주소값을 가지고 있기 때문에 감지하지 못함

1. 새로운 내용이 담긴 것이 tweet

2. 빈 배열에 tweet을 넣음 그리고 기존 전체 트윗을 복사해 옴

3. 빈 배열을 선언해서 다른 자료형으로 인지 

// TODO : useState를 react로 부터 import 합니다. - done!
import React, { useState } from 'react';
import Footer from '../Footer';
import Tweet from '../Components/Tweet';
import './Tweets.css';
import dummyTweets from '../static/dummyData';


const Tweets = () => {

  const getRandomNumber = (min, max) => {
    return parseInt(Math.random() * (Number(max) - Number(min) + 2));
  };

  // TODO : 새로 트윗을 작성하고 전송할 수 있게 useState를 적절히 활용하세요. - done!
  // [state 저장 변수, state 갱신 함수] = useState(상태 초기값);
  const [name, setName] = useState("parkhacker");
  const [contents, setContents] = useState("");
  const [tweets, setTweets] = useState(dummyTweets);

  // TODO : Tweet button 엘리먼트 클릭시 작동하는 함수를 완성.  - done!
  // 트윗 전송이 가능하게 작성해야 함

  const handleButtonClick = (event) => {

    const tweet = {
      id: dummyTweets.length+1,
      username: name,
      picture: `https://randomuser.me/api/portraits/women/${getRandomNumber(
        1,
        98
      )}.jpg`,
      content: contents,
      createdAt: new Date().toLocaleDateString('ko-kr'),
      updatedAt: new Date().toLocaleDateString('ko-kr'),
    };

      // 서로 다른 주소값을 가져서야 다른 자료형으로 인지해서
      // State가 다를 때 리렌더링을 함
      // 참조 자료형이 다른 자료값을 가지고 있어야 리렌더링이 된다는 것이 포인트

      // ! 1. 깊은 복사 방법 (내용 통으로 다 복사하는거, 주소만 복사하는게 아니라 값을 아예 다 복사)
      // const tweetList = tweets.slice(0);
      // tweetList.unshift(tweet);
      // setTweets(tweetList);

      // ! 2. Spread 문법 방법(깊은 복사 개념)
      // 같은 주소값을 가지고 있기 때문에 감지 하지 못함
      //1. 새로운 내용이 담긴 것이 트윗
      //2. 빈배열에 tweet을 넣음 그리고 기존 전체 트윗을 복사해 옴
      //3. 빈배을을 선언해서 다른 자료형으로 인지
      
      // tweets의 초깃값이 dummyTweets로 되어 있음
      setTweets([tweet, ...tweets])

    setTweets([tweet, ...tweets])
  };

  const handleChangeUser = (event) => {
    // TODO : Tweet input 엘리먼트에 입력 시 작동하는 함수를 완성 - done!
    setName(event.target.value);
  };

  const handleChangeMsg = (event) => {
    // TODO : Tweet textarea 엘리먼트에 입력 시 작동하는 함수를 완성 - done!
    setContents(event.target.value);
  };

  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"
                  value={name}
                  placeholder="your username here.."
                  className="tweetForm__input--username"
                  onChange={handleChangeUser}
                ></input>
                {/*TODO : 트윗을 작성할 수 있는 textarea 엘리먼트를 작성하세요.*/}
                <textarea
                  placeholder="your tweet here.."
                  className='tweetForm__input--message'
                  value={contents}
                  onChange={handleChangeMsg}
                ></textarea>
              </div>
              <div className="tweetForm__count" role="status">
                <span className="tweetForm__count__text">
                  {/* TODO : 트윗 총 개수를 보여줄 수 있는 Counter를 작성하기 - done!*/}
                  {`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) 갯수에 맞게 보여주기*/}
        { /*컴포넌트로 추출하면 <li> 요소 안이 아니라 컴포넌트에 써야 함 */ }
        { tweets.map((el) => { 
          return (
            <Tweet key={el.id} tweet={el} />
          );
          })}
      </ul>
      <Footer />
    </React.Fragment>
  );
};

export default Tweets;

 

 

 

Pages
About.js
import React from 'react';
import Footer from '../Footer';
import './About.css';

const About = (props) => {
  return (
    <section className="aboutTwittler">
      <div className="aboutTwittler__container">
        <div className="aboutTwittler__wrapper">
          <div className="aboutTwittler__detail">
            <p className="aboutTwittler__detailName">React Twittler Info</p>
          </div>
        </div>
      </div>
      <div className="aboutTwittler__content">
        <i className="fas fa-users"></i>
        <p>나만의 Twittler 소개페이지를 꾸며보세요.</p>
      </div>
      <Footer />
    </section>
  );
};

export default About;

 

 

 

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 = () => {
// TODO : 주어진 트윗 목록(dummyTweets)중 현재 유져인 parkhacker의 트윗만 보여줘야 합니다.
  const filteredTweets = dummyTweets.filter((tweet) => {
    return tweet.username === '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;