React Twitter Project

유슬기·2023년 1월 27일
0

프론트엔드

목록 보기
31/64
post-thumbnail

React Router 설치

  • React Router를 npm으로 설치해야 합니다.
npm install react-router-dom@6.3.0

상세 컴포넌트 구현하기

원하는 레이아웃에 끼워 넣을 수 있게 상세 컴포넌트를 먼저 구현합니다.

  • Sidebar 컴포넌트는 이미 구현되어 있습니다. Sidebar.js 파일에서 직접 확인하세요.
import React from 'react';

const Sidebar = () => {
  return (
    <section className="sidebar">
      <i className="far fa-comment-dots"></i>
      <i className="far fa-question-circle"></i>
      <i className="far fa-user"></i>
    </section>
  );
};

export default Sidebar;
  • Footer 컴포넌트의 후손 엘리먼트로 시멘틱 엘리먼트 <footer>가 있어야 합니다.
import React from 'react';

const Footer = () => {
  return (
  <footer>
{/* React에서 public 폴더 내 이미지파일을 불러올 때 src={`${process.env.PUBLIC_URL}/파일명} */}
    <img id="logo" src={`${process.env.PUBLIC_URL}/Twitter-logo.svg.png`} />
    Copyright @ 2023 Code States
  </footer>
  );
};

export default Footer;

Tweet 컴포넌트 (Tweet.js)

  • 각 트윗에 꼭 필요한 정보를 담고 있어야 합니다. (프로필 사진, 유저 이름, 트윗 생성 일자, 트윗 메시지)
    • 프로필 사진을 넣기 위해 <img> 엘리먼트를 작성하고, src 속성에 전달받은 props의 사진 정보가 들어가 있는지 확인해 주세요.

      <img src={tweet.picture} />
    • 유저 이름을 넣기 위해 <span> 엘리먼트를 작성하고, class 이름을 tweet__username로 지정하세요.

      <span className='tweet__username'>{tweet.username}</span>
    • 트윗 생성 일자를 넣기 위해 <span>엘리먼트를 작성하고, class 이름을 tweet__createdAt으로 지정하세요.

      • 날짜 형식은 yyyy. mm. dd. 이어야 하고, parsedDate 변수를 이용하세요.
      const parsedDate = new Date(tweet.createdAt).toLocaleDateString('ko-kr');
      
      <span className='tweet__createdAt'>{parsedDate}</span>
    • 트윗 메시지를 넣기 위해 <div> 엘리먼트가 작성되어 있습니다. class 이름을 tweet__message로 지정되어 있는지 확인해 주세요.

      • 트윗 메시지를 div.tweet__message의 textContent로 넣습니다.
      <div className="tweet__message">{tweet.content}</div>
  • dummyTweets가 아닌 다른 데이터가 props로 전달되어도 트윗 정보를 정확하게 표시해야 합니다.
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">
            <span className='tweet__username'>{tweet.username}</span>
            <span className='tweet__createdAt'>{parsedDate}</span>
          </div>
        </div>
        <div className="tweet__message">{tweet.content}</div>
      </div>
    </li>
  );
};

export default Tweet;

페이지 컴포넌트 구현하기

페이지를 구성하는 컴포넌트를 작성합니다.

이번 과제의 현재 유저는 parkhacker이기 때문에, Twittler의 MyPage에서 parkhacker의 트윗만 보여야 하는 요구사항을 만족해야 합니다.

About 컴포넌트 (About.js)

  • About 컴포넌트의 자식 컴포넌트로 Footer 컴포넌트가 있는지 확인해 주시고, 없다면 연결해 주세요.
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;

MyPage 컴포넌트 (MyPage.js)

  • 주어진 트윗 목록(dummyTweets)중 현재 유저인 parkhacker의 트윗만 보여야 합니다.
const filteredTweets = dummyTweets.filter(tweet => tweet.username === 'parkhacker');
  • const 변수로 선언된 filteredTweets을 활용해 parkhacker의 트윗이 보이도록 구현해 주세요.
  • MyPage 컴포넌트의 자식인 Tweet 컴포넌트에 props로 각 트윗의 정보(dummyTweets의 요소)가 전달되어야 합니다.
<ul className="tweets__mypage">
  {filteredTweets.map(tweet => {
		// Tweet 컴포넌트에 tweet, key를 props로 전달
    return <Tweet tweet={tweet} key={tweet.id} /> 
  })}
</ul>
  • MyPage 컴포넌트의 자식 컴포넌트로 Footer 컴포넌트가 있어야 합니다.
return (
    <section className="myInfo">
      ...(생략)
      <Footer />
    </section>
  );
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 => 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">
        {filteredTweets.map(tweet => {
          return <Tweet tweet={tweet} key={tweet.id} />
        })}
      </ul>
      <Footer />
    </section>
  );
};

export default MyPage;

Tweets 컴포넌트 (Tweets.js)

  • 하나의 트윗이 아니라, 주어진 트윗(dummyTweets) 개수에 맞게 보여줘야 합니다.
  • Tweets 컴포넌트의 자식 컴포넌트로 Footer 컴포넌트가 있어야 합니다.
<React.Fragment>
	...(생략)
	<ul className="tweets">
	  {dummyTweets.map((tweet) => {
	    return <Tweet tweet={tweet} key={tweet.id} />;
	  })}
	</ul>
	<Footer />
</React.Fragment>

React Router 적용하기

이전 React Twittler SPA 과제에서 배운 내용을 바탕으로 아래 기술 요구사항을 구현합니다.

React Router 컴포넌트 적용

  • Route path가 "/" 인 Tweets 컴포넌트가 있어야 합니다.
  • Route path가 "/about" 인 About 컴포넌트가 있어야 합니다.
  • Route path가 "/mypage" 인 MyPage 컴포넌트가 있어야 합니다.
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const App = (props) => {
  return (
    <BrowserRouter>
      <div className="App">
        <main>
          <Sidebar />
          <section className="features">
            <Routes>
              <Route path='/' element={<Tweets />} />
              <Route path='/about' element={<About />} />
              <Route path='/mypage' element={<MyPage />} />
            </Routes>
          </section>
        </main>
      </div>
    </BrowserRouter>
  );
};
  • React Router의 Link 컴포넌트가 3개 있어야 합니다.
  • Tweets 아이콘의 Link 컴포넌트는 "/" 로 연결되어야 합니다.
  • About 아이콘의 Link 컴포넌트는 "/about" 로 연결되어야 합니다.
  • MyPage 아이콘의 Link 컴포넌트는 "/mypage" 로 연결되어야 합니다.
import { Link } from 'react-router-dom';

const Sidebar = () => {
  return (
    <section className="sidebar">
      <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>
  );
};

React Router로 SPA 구현하기

  • 처음 접속 시, URL path가 / 이어야 합니다.
  • About 메뉴를 누르면 URL path가 /about으로 라우트 되어야 합니다.
  • Mypage 메뉴를 누르면 URL path가 /mypage로 라우트 되어야 합니다.

State, Props 활용 트윗 전송 Form 만들기

Tweets 컴포넌트에서 어떤 데이터가 state가 되어야 하고, 어떤 데이터를 props로 하위 컴포넌트에 전달해야 할지 고민하고 아래 기술 요구사항을 구현합니다.

Tweets.js 트윗 전송 Form 테스트

  • 유저 이름을 작성할 수 있는 input 엘리먼트가 있어야 합니다. (className : "tweetForm__input--username")
  • <input>의 값이 변경될 때 onChange 이벤트 핸들러가 불려야 합니다.
    const [username, setUsername] = useState("parkhacker");
    
    const handleChangeUser = (event) => {
      setUsername(event.target.value)
    };
    
    return (
    	<React.Fragment> 
    		...(생략)
    		<input
    		  type="text"
    		  placeholder="your username here.."
    		  className="tweetForm__input--username"
    		  onChange={handleChangeUser}
    		  value={username}
    		></input>
    		...(생략)
    	</React.Fragment>
    )
  • 트윗을 작성할 수 있는 textarea 엘리먼트가 있어야 합니다. (className : "tweetForm__input--message")
  • <textarea>의 값이 변경될 때 onChange 이벤트 핸들러가 불려야 합니다.
    const [msg, setMsg] = useState("");
    
    const handleChangeMsg = (event) => {
      setMsg(event.target.value)
    };
    
    return (
    	<React.Fragment>
    		...(생략)
    		<textarea
          placeholder="트윗 내용을 입력하세요."
          className="tweetForm__input--message"
          value={msg}
          onChange={handleChangeMsg}
        ></textarea>
    		...(생략)
    	</React.Fragment>
    )
  • 트윗을 전송할 수 있는 button 엘리먼트가 있어야 합니다. (className : "tweetForm__submitButton")
  • <button>의 값이 변경될 때 onClick 이벤트 핸들러가 불려야 합니다.
    const [tweets, setTweets] = useState(dummyTweets);
    
    const handleButtonClick = () => {
        const tweet = {
          id: tweets.length + 1,
          username: username,
          picture: `https://randomuser.me/api/portraits/men/98.jpg`,
          content: msg,
          createdAt: new Date(),
          updatedAt: new Date()
        };
        setTweets([tweet, ...tweets]); // 새로운 tweet를 앞에, 기존 tweets는 스프레드 연산자로 복사
    		setMsg(''); // button 클릭 시 textarea의 value 초기화
    
    return (
    	<React.Fragment>
    		...(생략)
    		<button
    		  type="button"
    		  className="tweetForm__submitButton"
    		  onClick={handleButtonClick}
    		>
    		  전송
    		</button>
    		...(생략)
    	</React.Fragment>
    )
  • 유저 이름과 트윗을 작성하고, 트윗 버튼을 누르면 새로운 트윗이 추가되어야 합니다.
    • 기존 dummyTweets를 모두 보여줘야 합니다.

    • 새로 추가된 트윗을 포함하여 보여줘야 합니다.

    • 새로 추가된 트윗이 최상단에 위치하여야 합니다.

      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 [tweets, setTweets] = useState(dummyTweets);
        const [username, setUsername] = useState("parkhacker");
        const [msg, setMsg] = useState("");
      
        const handleButtonClick = () => {
          const tweet = {
            id: tweets.length + 1,
            username: username,
            picture: `https://randomuser.me/api/portraits/men/98.jpg`,
            content: msg,
            createdAt: new Date(),
            updatedAt: new Date()
          };
          setTweets([tweet, ...tweets]);
      		setMsg('');
        };
      
        const handleChangeUser = (event) => {
          setUsername(event.target.value)
        };
      
        const handleChangeMsg = (event) => {
          setMsg(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"
                        placeholder="your username here.."
                        className="tweetForm__input--username"
                        onChange={handleChangeUser}
                        value={username}
                      ></input>
                      <textarea
                        placeholder="트윗 내용을 입력하세요."
                        className="tweetForm__input--message"
                        value={msg}
                        onChange={handleChangeMsg}
                      ></textarea>
                    </div>
                    <div className="tweetForm__count" role="status">
                      <span className="tweetForm__count__text">
                        {"total: " + tweets.length}
                      </span>
                    </div>
                  </div>
                  <div className="tweetForm__submit">
                    <div className="tweetForm__submitIcon"></div>
                    <button
                      type="button"
                      className="tweetForm__submitButton"
                      onClick={handleButtonClick}
                    >
                      전송
                    </button>
                  </div>
                </div>
              </div>
            </div>
            <div className="tweet__selectUser"></div>
            <ul className="tweets">
              {tweets.map((tweet) => {
                return <Tweet tweet={tweet} key={tweet.id} />;
              })}
            </ul>
            <Footer />
          </React.Fragment>
        );
      };
      
      export default Tweets;
profile
아무것도 모르는 코린이

0개의 댓글