React twittler state props 과제

Dongwoo Joo·2023년 3월 27일
0

codestates bootcamp

목록 보기
21/48
post-thumbnail

글의 목적

웹 어플리케이션의 코드를 이해하고, 사용 가능하도록 이 코드에 익숙해지기 위함이다.

Twittler 구성(조직도)

웹 애플리케이션의 구조를 파악하기 위해, vscode의 파일을 모두 작성했다. 그리고 사용자의 동작에 따라 UI를 어떻게 변형시키는지 그렸다(같은 과제를 연속으로 해서 2번 그렸는데, 머리에서 전체적인 그림을 그리는데 도움이 많이 되었다).
각 컴포넌트들의 인과관계에 따라 요소들을 간단하게 선으로 그려보고, 컴포넌트를 트리 구조로 구성했다.

웹 어플리케이션 구조 파악


컴포넌트를 트리로 구성

코드 리뷰(의사코드 작성)

Index.js

import React from 'react';        // react 패키지에서 react 라이브러리를 가져온다. React는 React 컴포넌트를 빌드하는데 사용되는 핵심 라이브러리이다.
import ReactDOM from 'react-dom'; // 설치된 React 패키지에서 ReactDOM 라이브러리를 가져온다. ReactDOM은 React 컴포넌트를 브라우저에 렌더링하는데 사용되는 라이브러리이다.
import App from './App';          // 동일한 디렉토리에 있는 App.js 파일에서 App component를 가져온다. ./에서 .은 같은 폴더를 의미한다.

import dummyTweets from './static/dummyData'; // static 디렉토리에 있는 dummyTweets 데이터를 가져온다.

ReactDOM.render(                    
  <App dummyTweets={dummyTweets} />,
  document.getElementById('root')
);

// ReactDOM.render 메서드는 App 컴포넌트를 HTML 문서의 root element로 렌더링하기 위해 호출된다.
// dummyTweets 데이터는 App 컴포넌트의 prop으로 전달된다.
// 이 메서드(ReactDOM.render)는 두 개의 인수를 사용한다.
// 첫 번째 인수는 렌더링 해야 하는 React 컴포넌트이다.
// 두 번째 인수는 컴포넌트가 렌더링 되어야 하는 DOM 요소이다. ID가 root인 요소이다.
// 전반적으로 이 코드는 필요한 react 및 reactDOM 라이브러리를 가져오고, App 컴포넌트와 dummyTweets 데이터를 가져온 다음,
// App 컴포넌트를 HTML 문서의 root 요소에 렌더링 하여 dummyTweets 데이터를 prop으로 전달한다.
// 이는 React 앱의 entry point이고, 앱의 첫 번째 컴포넌트 렌더링을 담당한다.

App.js

import React from 'react';  // React 라이브러리를 가져와 파일에서 사용할 수 있도록 한다.
import { BrowserRouter, Routes, Route } from 'react-router-dom'; // react-router-dom 라이브러리에서 BrowserRouter, Routes, Route 컴포넌트를 가져온다.

import Sidebar from './Sidebar'; // 같은 디렉토리에 있는 Sidebar.js 파일에서 Sidebar 컴포넌트를 가져온다.
import Tweets from './Pages/Tweets'; // Page 디렉토리의 Tweets.js 파일에서 Tweets.js 컴포넌트를 가져온다.

import About from './Pages/About' // Page 디렉토리의 About.js 파일에서 About.js 컴포넌트를 가져온다.
import MyPage from './Pages/MyPage'; // Page 디렉토리의 MyPage.js 파일에서 MyPage.js 컴포넌트를 가져온다.

import './App.css'; // App.css 파일을 가져온다(App 컴포넌트에 특정한 CSS 스타일이 포함되어 있는 파일)
import './global-style.css'; // global-style.css 파일을 가져온다(전체 앱에 대한 전역 CSS 스타일이 포함된 파일)

const App = (props) => { // App 컴포넌트를 props를 매개변수로 받는 기능적 컴포넌트로 정의한다.
  return ( // 앱 UI 에서, 경로에 따라 다른 뷰를 보여주기 위해, React Router 라이브러리를 사용한다. React Router 라이브러리에는 4개의 컴포넌트가 있다. BrowserRouter, Routes, Route, Link
    <BrowserRouter> {/* 앱에서 client-side routing을 활성화하는 BrowserRouter 컴포넌트 안에 전체 컴포넌트를 감싼다. */}
    <div className="App"> {/* 클래스 이름이 App인 div 요소로 감싼다 */}
      <main> {/* main 태그로 감싼다 */}
        <Sidebar /> {/* 앱 UI에서 Sidebar 컴포넌트를 렌더링한다. */}
        <section className="features"> {/* 클래스 이름이 feature인 section 요소에 Route 요소를 감싼다. */}
          <Routes> {/* Routes 요소는 경로와 컴포넌트를 지정하는 Route 요소를 감싼다 */}
          <Route path='/' element={<Tweets />} /> {/* Route 컴포넌트를 사용해서 경로에 따른 컴포넌트를 매칭해준다. */}
          <Route path='/mypage' element={<MyPage />} /> 
          <Route path='/about' element={<About />} />
          {/* 실수: mypage 앞에 / (슬래시)를 붙이지 않아서 Link 되지 않았고, 라우트 되지도 않았던 것임. */}
          {/* Route 내 Path로 주소 경로를 표시, element로 컴포넌트를 표시 */}        
          </Routes>
        </section>
      </main>
    </div>
    </BrowserRouter>
  );
};

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


 // Import의 기능: 구체적으로 확인 필요

 // The summary of App.js file
 // React application에서 root 컴포넌트 역할을 하는 컴포넌트이다.
 // react-router-dom 라이브러리에서 제공하는 BrowserRouter와 Routes 컴포넌트를 사용해서, 애플리케이션의 route를 정의한다.
 // Sidebar, Tweets, Mypage, About 컴포넌트를 포함하고 있다.
 // Routes 컴포넌트는 URL 경로를 해당하는 컴포넌트로 매핑하고, 이를 앱의 UI에 렌더링한다.
import React from 'react'; // 반복 코드
import { Link } from 'react-router-dom'; // 반복 코드

const Sidebar = () => { // Sidebar 라는 컴포넌트를 정의한다.
  return (
    <section className="sidebar"> {/* 클래스 이름이 sidebar인 section 요소를 생성하고, Link 컴포넌트를 감싼다. */}
      <Link to="/"><i className="far fa-comment-dots"></i></Link> {/* Link 컴포넌트를 생성한다. 경로를 변경하는 역할을 한다.*/}
      <Link to="/about"><i className="far fa-question-circle"></i></Link> {/* i 태그를 생성하고, font-awesome 라이브러리에서 불러온 아이콘 클래스 이름을 작성한다.*/}
      <Link to="/mypage"><i className="far fa-user"></i></Link> {/* 아이콘을 클릭 시, Link 컴포넌트의 경로로 이동한다. */}
    </section>
  );
};

export default Sidebar; // Sidebar 컴포넌트를 파일의 기본 내보내기로 내보낸다.
// 즉, import Sidebar from './Sidebar'를 사용하여 이 컴포넌트를 다른 파일로 가져올 때 가져온 값은,
// Sidebar 컴포넌트 자체가 되며 단순히 <Sidebar />를 렌더링하여 가져오는 파일에서 사용할 수 있다.
// 내보내기 기본 구문은 파일당 한 번만 사용할 수 있으며, 응용 프로그램의 다른 파일에서 구성 요소(또는 함수, 개체 등)를 사용할 수 있도록 하는 방법이다.

// Summary
// Sidebar 컴포넌트는 앱의 다른 페이지로 이동하는 링크가 있는 섹션을 렌더링 하는 함수형 컴포넌트이다.
// react-router-dom 라이브러리에서 Link 컴포넌트를 가져와서 탐색(경로 변경)을 처리한다.
// 컴포넌트 내부에서는 세 개의 링크가 생성된다. 각 링크는 앱의 다른 페이지에 해당하는 서로 다른 URL을 가리킨다.
// 링크는 font-awesome 라이브러리의 아이콘으로 표시된다.


// 과제
// TODO : React Router DOM의 Link 컴포넌트를 import 합니다.
// TODO : Link 컴포넌트를 작성하고, to 속성을 이용하여 경로(path)를 연결합니다.

Tweets.js

import React, { useState } from 'react'; // React 라이브러리에서 useState hook을 가져온다.
import Footer from '../Footer'; // footer 컴포넌트 가져오기
import Tweet from '../Components/Tweet'; // Tweet 컴포넌트 가져오기
import './Tweets.css'; // Tweet.css CSS 파일 가져오기.
import dummyTweets from '../static/dummyData'; // dummyTweets 데이터 가져오기.
// 반복 사항 

const Tweets = () => {   // 화살표 함수로 tweet 컴포넌트를 정의.
  const [username, setUsername] = useState('parkhacker'); // useState가 hook이다.
  // username이 상태 저장 변수이고, setUsername 은 상태 갱신 함수이다. 그리고 상태 초기 값은 'parkhacker'이다.
  const [tweets, setTweets] = useState(dummyTweets); // hook을 사용해서, username, tweets, tweetText 3가지 상태 변수와 각각의 상태 setter을 만든다.
  const [tweetText, setTweetText] = useState(''); // username과 tweets는 값으로 초기화되지만, tweetText는 초기에 빈 문자열이다.
//const [state 저장 변수, state 갱신 함수] = useState(상태 초기 값);  

  const handleButtonClick = (event) => { // 이 이벤트 함수는 Tweet 버튼을 클릭했을 때, 호출된다.
    event.preventDefault(); // event.preventDefault() 메서드는 버튼을 클릭할 때 브라우저의 기본 동작을 중지하는 데 사용된다.
    // 기본적으로 버튼을 클릭하면 양식이 제출되거나 페이지가 다시 로드되지만 preventDefault() 메서드는 이를 방지한다.
    // 이 경우 버튼은 트윗을 게시하는 데 사용되며 preventDefault()를 사용하지 않는 경우 버튼을 클릭하면 페이지가 다시 로드되므로 바람직하지 않다. 추가적으로 확인할 것
    // 따라서 여기서는 event.preventDefault() 메서드를 사용하여 페이지가 다시 로드되는 것을 방지하고 중단 없이 트윗을 게시할 수 있다.
 
    const newTweet = { // username 및 tweetText의 현재 상태를 사용해서 새 tweet 객체를 만들고, tweets 배열에 추가하고, tweetText 상태를 빈 문자열로 재설정한다.
      id: tweets.length + 1, // 현재 tweets는 dummyTweets이다. 새로운 트윗이 추가되면, 키값을 추가해야 하기 때문에, 기존 배열의 길이 + 1로 한 것이다.
      picture: "https://randomuser.me/api/portraits/men/98.jpg",
      username: username,
      content: tweetText,
      createdAt: new Date().toISOString()
    };

    const newTweets = [newTweet, ...tweets] // Tweet 버튼 클릭 시, 새로운 배열을 생성한다. newTweet을 배열 맨 앞에 위치시킨다. ... 스프레드 메서드로 나머지 tweets를 뒤에 위치시킨다.
    setTweets(newTweets);       // 위 배열로 상태가 갱신된다.
    setTweetText('');           // 새로운 Tweet을 생성함과 동시에 작성한 텍스트를 지우기 위해, username 및 tweetText 필드에 작성한 텍스트를 빈 문자열로 바꾼다.
    setUsername('');
  };

  const handleChangeUser = (event) => { // 사용자가 username 입력 필드에 텍스트를 입력할 때, username 상태 변수를 업데이트 한다.
    setUsername(event.target.value); // 여기에서 event.target.value가 가리키는 것은 이벤트를 트리거한 입력 요소 값을 나타낸다.
  };                                 // 사용자가 입력한 username으로 사용자 이름 상태를 갱신한다. 즉, 이벤트 타겟 값은 사용자가 작성한 username 이다.

  const handleChangeMsg = (event) => { // 사용자가 tweetText 입력 필드에 텍스트를 입력할 때, tweetText 상태 변수를 업데이트 한다.
    setTweetText(event.target.value); // event.target.value는 사용자가 입력한 트윗 내용이다. 입력한 tweetText로 기존에 빈 문자열인 상태 값을 갱신한다.
  };

  return (  // return 키워드는 컴포넌트의 렌더링 기능을 시작한다.
  // Fragment 컴포넌트는 DOM에 추가 노드를 추가하지 않고, 여러 자식을 함께 그룹화 할 수 있다.
    <React.Fragment> {/* 이 코드는 트윗을 만들고 표시하기 위한 양식을 렌더링하는 React의 컴포넌트이다. */}
      <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 // input 태그로 username을 작성하는 form을 만든다.
                  type="text" // type은 표시될 입력 컨트롤 유형을 지정하는 <input> 요소의 속성이다. 이 경우 입력 컨트롤이 사용자가 텍스트를 입력할 수 있는 텍스트 상자여야 함을 지정한다.
                  // 이 특별한 경우에 type="text" 속성은 사용자가 텍스트를 입력할 수 있는 입력 필드를 렌더링하도록 브라우저에 지시하고 사용자가 입력한 값은 handleChangeUser 이벤트 핸들러에 의해 캡처되어 사용자 이름 상태 변수에 저장된다. useState 후크를 사용한다.
                  placeholder="your username here.." // placeholder는 사용자에게 작성할 내용에 대한 가이드를 form에 보여준다.
                  className="tweetForm__input--username" // 클래스 이름
                  value={username} // input의 value: 상태 변수를 적는다. 암기 사항. username에 값을 설정한다. 이 값은 username 상태 변수에서 가져온다.
                  onChange={handleChangeUser} // onChange에는 상태 갱신 함수를 적는다. onChange 이벤트 리스너는 handleChangeUser 함수를 호출한다. 이 함수는 사용자가 username에 입력할 때마다 username 상태 변수의 값을 업데이트 한다.
                ></input>  {/* 즉, 사용자가 이름을 적는 이벤트가 발생하면, 이벤트 함수를 호출하고, 이벤트 함수 내부에 있는 상태 갱신 함수가 호출되고, value = {username}으로 작성해서, event.target.value는 username이 되어, username으로 상태가 업데이트 되는 것이다. */}
                <textarea // 이 컴포넌트는 사용자가 트윗을 입력할 수 있는 텍스트 에어리어를 렌더링하며, 사용자가 텍스트 에어리어에 입력할 때마다 'tweetText' 상태 변수의 값을 업데이트한다.
                className='tweetForm__input--message'
                placeholder="What's happening?"
                value={tweetText} // 컴포넌트는 value 속성을 사용해서 textarea의 값을 설정한다. 이 값은 tweetText 상태 변수에서 가져온다.
                onChange={handleChangeMsg} // onChange 이벤트 리스너를 textarea에 추가한다. 이 리스너는 handleChangeMsg 함수를 호출한다. 이 함수는 사용자가 textarea에 입력할 때마다 tweetText 상태 변수의 값을 업데이트 한다.
                ></textarea>  {/* 입력 시, "바뀌기 위해" onChange 이벤트 리스너 */}
              </div>
              <div className="tweetForm__count" role="status">
                <span className="tweetForm__count__text">
                  {'total: ' + tweets.length} 
                  {/* 총 tweet의 개수를 total로 표현 */}
                </span>
              </div>
            </div>
            <div 
            className="tweetForm__submitButton">
              <div className="tweetForm__submitIcon"></div>
              <button // 작성한 Tweet을 업데이트 하기 위한 버튼 생성
              className='tweetForm__submitButton'
              onClick={handleButtonClick}>Tweet</button> {/* onClick 이벤트 리스너 -> Tweet 버튼 클릭 시 handleButtonClick 함수 호출한다. */}
            </div> {/* "무언가를 클릭" 하는 이벤트 리스너 onClick */}
          </div>
        </div>
      </div>
      <div className="tweet__selectUser"></div>
      <ul className="tweets">
        {tweets.map((tweet) => ( // tweets 배열을 매핑하고, tweet 컴포넌트의 새 배열을 반환한다.
          <Tweet key={tweet.id} tweet={tweet} /> // map 메서드를 사용해서, 기존 dummy 트윗 + 새롭게 입력되는 트윗에 키를 할당해 컴포넌트를 호출한다.
        ))}  {/* tweets 배열의 각 트윗 객체에 대해 tweet 컴포넌트가 생성되고, key 및 tweet의 두 가지 props가 전달된다.
        key props는 tweet의 id로 설정되며, react에서 개별 요소를 추적하는데 사용된다.
        tweet prop은 전체 tweet 객체로 설정되며, tweet 컴포넌트에서 각 tweet의 content를 렌더링하는데 사용된다. */}
      </ul>
      <Footer /> {/* footer 컴포넌트 호출 */}
    </React.Fragment>
  );
};

export default Tweets;

// Summary
// 이 코드는 트윗 피드를 나타내는 Tweets 라는 함수형 컴포넌트를 정의한다.
// React 및 useState와 같은 일부 종속성과 Footer 및 Tweet과 같은 몇 가지 컴포넌트를 가져온다.
// 컴포넌트는 useState hook을 사용하여 username, tweet 및 tweetText의 상태를 관리합니다.
// 또한 handleButtonClick, handleChangeUser 및 handleChangeMsg와 같은 여러 이벤트 핸들러를 정의한다. 
// 마지막으로 컴포넌트는 새 트윗을 생성하는 양식으로 트윗 피드를 렌더링한다.


{/* React.Fragment는 무엇인가? */}

// 실시간 세션
// 리액트는 컴포넌트를 대문자로 적는다. 꼭 지켜야한다. 복수형으로 적는다. 왜?
// 상태 업데이트 할 때, 무조건 상태 갱신 함수 써야합니다.

// [][].push하면, 화면에 제대로 적용이 되지 않습니다. => 1, 2번은 되기도 하는데 React가 이 동작을 보장하지 않는다.
// 상태 갱신함수 쓰면 잘 되도록 이미 뒤에다가 다 만들어 놨다.
// 배열에 푸시를 직접하면 안되고, 새로운 배열을 만들고, 할당해야 한다.

// const newTweets = [newTweet, ...tweets] 앞에 새로운 트윗을 넣으려면, 이렇게 작성
// const newTweets = [...tweets, newTweet] 뒤에 새로운 트윗을 넣으려면, 이렇게 작성

// const handleChangeUser = (event) => {
//   setUsername(event.target.value); 
  // Input => value =  상태 변수, OnChange = 상태 갱신 함수를 포함하는 이벤트 핸들러를 전달해준다.
  // input onChange 이벤트가 언제 발생하냐? -> input의 값을 변경할 때 작동한다.
  // 아 그러면, handleChangeUser 이벤트 핸들러 함수에는 상태 갱신을 시키는 로직이 꼭 필요하겠구나!
  // DOM 할 때도, input.onchange = (event) => {}
  // 상태 갱신함수를 적지 않으면, 개발자 도구 콘솔에 값이 입력되지만, 화면 변경을 해주지 않음.
// };

// const Tweets = () => {   
//   const [username, setUsername] = useState('parkhacker');
//   const [tweets, setTweets] = useState(dummyTweets);
//   const [tweetText, setTweetText] = useState('');
  // useState로 상태 변수들 정의. 상태 변수들은 set이 붙어 있는 상태 갱신 함수로 바꾼다.

//   <ul className="tweets">
//   {tweets.map((tweet) => (
//     <Tweet key={tweet.id} tweet={tweet} />
//   ))}
//   {/* map을 사용하는 이유: JSX 엘리먼트를 담고자 하기 때문이다.  
//   map 사용할 때 주의할 점: key 를 적어줘야 한다. Key를 통해서, tweet이 다시 생성되는 것을 막아준다.
//   1, 2, 3 => 1, 2, 3, 4
//   key 이름에 따라서 다시 랜더링할지 안할지 결정한다.
//   이름을 정하지 않으면, 처음부터 3개를 다시 생성한다.
//   이름을 정해주면, 기존에서 4를 추가로 생성한다. */}
// </ul>
  

// 과제
// TODO : useState를 react로 부터 import 합니다.
// TODO : 새로 트윗을 작성하고 전송할 수 있게 useState를 적절히 활용하세요.
// TODO : Tweet button 엘리먼트 클릭시 작동하는 함수를 완성하세요.
// 트윗 전송이 가능하게 작성해야 합니다.
// TODO : Tweet input 엘리먼트에 입력 시 작동하는 함수를 완성하세요.
// TODO : Tweet textarea 엘리먼트에 입력 시 작동하는 함수를 완성하세요.
// TODO : 트윗을 작성할 수 있는 textarea 엘리먼트를 작성하세요.
// TODO : 트윗 총 개수를 보여줄 수 있는 Counter를 작성하세요.
// TODO : 작성한 트윗을 전송할 수 있는 button 엘리먼트를 작성하세요.
// TODO : 하나의 트윗이 아니라, 주어진 트윗 목록(dummyTweets) 갯수에 맞게 보여줘야 합니다.

70% 끝. 이어서 추가적으로 작성

profile
pursue nature

0개의 댓글