본문 바로가기

[React] React Twitter Intro - 컴포넌트 기반 개발하기

[React] React Twitter Intro - 컴포넌트 기반 개발하기

컴포넌트 위주로 간단히 구현한 결과물

더보기
import React from 'react';
import './App.css';
import './global-style.css';
import { dummyTweets } from './static/dummyData';
// ! 위 코드는 수정하지 않습니다.

const Sidebar = () => {
 

  return (
    <section className="sidebar">
      <i className="fa-solid fa-circle-notch far fa-comment-dots"></i>



      <i className="fa-brands fa-twitter fa-lg" ></i>
    </section>
  );
};

const Counter = () => {
  return (
    <div className="tweetForm__input">
      <div className="tweetForm__inputWrapper">
        <div className="tweetForm__count" role="status">
          total = {dummyTweets.length}
        </div>
      </div>
    </div>
  );
};

const Footer = () => {
  return (
    
      <footer>                      
        {/* // div 안에 */}
      <img id="logo" src={`${process.env.PUBLIC_URL}/codestates-logo.png`} />
      
      Copyright @ 2022 Code States
      </footer>
    
  );
};
// TODO : Footer 함수 컴포넌트를 작성합니다. 시멘틱 엘리먼트 footer가 포함되어야 합니다.

const Tweets = () => {
  return (
    <ul className="tweets">

      {dummyTweets.map((tweet) => {

        const isParkHacker = tweet.username === 'parkhacker'    
        const tweetUserNameClass = isParkHacker                 
          ? 'tweet__username tweet__username--purple'
          : 'tweet__username' ;                              


        return (
          <li className="tweet" key={tweet.id}>
            <div className="tweet__profile">
              <img src = {tweet.picture}></img>       
              {/* img의 경우 <img>  </img> 빈칸이 생기면 자식요소라고 판단하여 오류를 낸다 */}

            </div>

            <div className="tweet__content">
              <div className="tweet__userInfo">
                <span className={tweetUserNameClass}>{tweet.username}</span>
                <span className="tweet__createdAt">{tweet.createdAt}</span>                
              </div>
              <div className='tweet__message'>{tweet.content}</div>
            </div>
          </li>
        );
      })}
    </ul>
  );
};

const Features = () => {
  return (
    <section className="features">
      <div className="tweetForm__container">
        <div className="tweetForm__wrapper">
          <div className="tweetForm__profile"></div>
          <Counter />
        </div>
      </div>
      <Tweets />
      <Footer />
    </section>
  );
};

const App = () => {
  return (
    <div className="App">
      <main>
        <Sidebar />
        <Features />
      </main>
    </div>
  );
};

// ! 아래 코드는 수정하지 않습니다.
export { App, Sidebar, Counter, Tweets, Features, Footer };

 

File Structure

React Twittler Intro의 파일 구조는 아래와 같다.

/
├── /React Twittler State Props
│   ├── README.md
│   ├── /public                    # create-react-app이 만들어낸 파일로 yarn/npm start로 실행 시에 쓰인다
│   └── /src                       # React 컴포넌트가 들어가는 폴더
│        ├── static                # dummyData가 들어가는 폴더
│        │    └── dummyData.js
│        ├── App.css
│        ├── App.js                # Twittler App이 작성되어 있다.
│        ├── index.js
├  package.json
└ .gitignore

 

 

./static/dummyData.js

연습용 더미 데이터는 ‘./static/dummyData.js’에 저장되어 있다. ‘src/App.js’ 파일 최상단에 dummyTweets가 import 된다. 

import React from 'react';
import './App.css';
import { dummyTweets } from './static/dummyData';
// **예시
  {
    id: 1,
    username: 'kimcoding',
    picture: `https://randomuser.me/api/portraits/women/1.jpg`,
    content: '모든 국민은 인간으로서의 ... ',
    createdAt: '2019-02-24T16:17:47.000Z',
    updatedAt: '2019-02-24T16:17:47.000Z'
  }

 

parameter 형식 
id 숫자 고유한 아이디
username 문자열 사용자 이름
picture 문자열 프로필 사진의 URL 주소
content 문자열 사용자가 작성한 트윗 내용
createdAt 문자열 UTC 형태의 시간 정보
updatedAt 문자열 UTC 형태의 시간 정보

 

 

./index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://kit.fontawesome.com/f6f94e451b.js" crossorigin="anonymous"></script>

index.js 파일은 {App} 이라는 객체를 가져와서 render 함수가 우리가 작업할 app.js 파일을 돔처럼 세팅하여 렌더링을 해준다. 

 

./App.js

import React from 'react';
import './App.css';
import './global-style.css';
import { dummyTweets } from './static/dummyData';
// ! 위 코드는 수정하지 않습니다.

const Sidebar = () => {
  return (
    <section className="sidebar">
      <i className="fa-solid fa-circle-notch far fa-comment-dots"></i>
      <i className="fa-brands fa-twitter fa-lg"></i>
    </section>
  );
};
.
.
.
.
.
.
.
.

실제로 컴포넌트를 제작하여 작업하는 파일이다.


 

상세 컴포넌트 구현하기

 

컴포넌트 그리기

본격적으로 개발하기 전에 div wrapper 등을 제외하고 컴포넌트 끼리의 구조만 그려보았다. 확실히 구조를 먼저 알고 개발을 시작하니 한결 수월했다. 

코드로 정리하기

const Features = () => {
  return (
    <section className="features">
      <div className="tweetForm__container">
        <div className="tweetForm__wrapper">
          <div className="tweetForm__profile"></div>
          <Counter />    🟣 Feature 컴포넌트의 후손 컴포넌트로 Counter 컴포넌트가 있다.
        </div>
      </div>
      <Tweets />         🟣 Features 컴포넌트의 후손 컴포넌트로 Tweets 컴포넌트가 있다. 
      <Footer />         🟣 Features 컴포넌트의 후손 컴포넌트로 Footer 컴포넌트가 있다.
    </section> 
  );
};

const App = () => {
  return (
    <div className="App">
      <main>
        <Sidebar />     🟣 App 컴포넌트의 후손 컴포넌트로 Sidebar 컴포넌트가 있다.
        <Features />    🟣 App 컴포넌트의 후손 컴포넌트로 Features 컴포넌트가 있다.
      </main>
    </div>
  );
};

 


 

조건부 렌더링  

 

조건부 렌더링을 활용해서 여러 트윗 중 유저 이름이 parkhacker이면 유저 이름의 배경색이 연보라색이 되도록 클래스(tweet__username--purple)를 지정해야 한다. 

 

Twittler에서는 특별한 역할을 가진 사람의 경우 username을 옅은 보라색으로 배경색을 변경해야 한다. 

parkhacker 배경색이 연보라색이다.
.tweet__username--purple {
  background-color: rgb(235, 229, 249);
}

 

 

parkhacker의 배경색을 변경하기 위해 dummyTweets에서 username이 parkhacker인 경우를 찾아야 한다. 먼저 dummyTweets가 (1) 어떤 데이터 타입을 가지고 있고, (2) 어떻게 JSX 안에서 불러와 사용되고 있는지 먼저 확인할 필요가 있다. 

 

(1) 위에서 확인했듯이 dummyTweets은 배열이고, 그 안의 element는 id, username, picture 등의 key와 그에 대응하는 각각의 값(value)을 가진 객체이다.

 

(2) Tweets 컴포넌트의 return문 안에는 tweets라는 className을 가진 ul tag 가 있고, ul 태그 안에 dummyTweets의 element (tweet)들이 map 메서드에 의해 li 태그 안에 삽입되고 있다. 즉, map 메서드에 의해 li 태그에 삽입되고 있는 엘리먼트, 즉 tweet의 타입은 바로 객체이다. 객체의 값을 사용하려면 dot notation을 이용하면 된다.

 

const Tweets = () => {
  return (
    <ul className="tweets">
      {dummyTweets.map((tweet) => {
	// console.log(tweet)
        return (
          <li className="tweet" key={tweet.id}>
            <div className="tweet__profile"></div>
	         ...
          </li>
        );
      })}
    </ul>
  );
};

 

우선 각각의 tweet 중에서 usename이 parkhacker인 경우를 새로운 변수에 할당한다.

const isParkHacker = tweet.username === 'parkhacker'

 

다음은 삼항 연산자를 이용하여 tweet.username 이 parkhacker 인 경우와 그렇지 않은 경우에 대해 className 을 지정해준다.

const tweetUserNameClass = isParkHacker
          ? 'tweet__username tweet__username--purple'
          : 'tweet__username';

 

마지막으로 각각의 tweet이 렌더링 되고 있는 li tag 안에 username에 따라 배경색이 달라질 수 있도록 적용하면 된다. 중괄호 {}를 이용한 JSX 문법은 태그와 태그 사이는 물론 className처럼 태그 안에 들어가는 속성의 값에도 사용할 수 있다.

<li className="tweet" key={tweet.id}>
  <span className={tweetUserNameClass}>{tweet.username}</span>
</li>

 

 

import "./styles.css";
import { dummyTweets } from "./dummyData";

const App = () => {
  return (
    <ul className="tweets">
      {dummyTweets.map((tweet) => {
      
        🟣 username 이 'parkhacker'인 경우를 할당한 isParkHacker 변수와
        🟣 삼항연산자를 사용하여 className 을 지정한
        🟣 tweetUserNameClass 변수를 이곳에 사용한다.

        return (
          <li className="tweet" key={tweet.id}>
            <span>
            
              🟣 {/* span tag 안에 className 을 지정하고,
              🟣 span tag 사이에는 username이 rendering 되도록 한다. */}
              
            </span>
            
          </li>
        );
      })}
    </ul>
  );
};
export default App;

 

 


 

컴포넌트 기능 구현하기

 

Counter 컴포넌트 

  • dummyTweet로 전달되는 트윗 개수를 나타내는 카운트가 있다. ex) total : 5
const Counter = () => {
  return (
    <div className="tweetForm__input">
      <div className="tweetForm__inputWrapper">
        <div className="tweetForm__count" role="status">
          total = {dummyTweets.length}
        </div>        
      </div>
    </div>
  );
};

 

  •   

Tweets 컴포넌트 

  • 트윗 저자의 프로필 사진이 있어야 한다다.
    • 방법  :  li.tweet 엘리먼트의 후손 엘리먼트로 <img> 엘리먼트를 생성하고 dummyTweets의 이미지 주소 정보를 찾아서 <src> 속성을 지정하면 된다.
  • 유저 이름이 있어야 한다.
    • 방법  :  li.tweet 엘리먼트의 후손 엘리먼트로 <span> 엘리먼트를 생성하고 dummyTweets의 유저 이름을 <span>의 텍스트 콘텐츠로 넣는다.
  • 트윗 생성 일자(yyyy. m. d.) 가 있어야 다.
    • 방법  :  li.tweet 엘리먼트의 후손 엘리먼트로 <span> 엘리먼트를 생성하고 dummyTweets의 트윗 생성 일자를 <span>의 텍스트 콘텐츠로 넣는다. 
  • 트윗 메시지가 있어야 다.
    • 방법  :  li.tweet 엘리먼트의 후손 엘리먼트로 <div> 엘리먼트를 생성하고 dummyTweets의 트윗 내용을 <div> 의 텍스트 콘텐츠로 넣는다.
  • 트윗이 dummyTweets의 길이만큼 있어야 한다. 

 

완성된 코드

const Tweets = () => {
  return (
    <ul className="tweets">

      {dummyTweets.map((tweet) => {

        const isParkHacker = tweet.username === 'parkhacker'    
        const tweetUserNameClass = isParkHacker                 
          ? 'tweet__username tweet__username--purple'
          : 'tweet__username' ;                              


        return (
          <li className="tweet" key={tweet.id}>
            <div className="tweet__profile">
              <img src = {tweet.picture}></img>       
              {/* img의 경우 <img>  </img> 빈칸이 생기면 자식요소라고 판단하여 오류를 낸다 */}

            </div>

            <div className="tweet__content">
              <div className="tweet__userInfo">
                <span className={tweetUserNameClass}>{tweet.username}</span>
                <span className="tweet__createdAt">{tweet.createdAt}</span>                
              </div>
              <div className='tweet__message'>{tweet.content}</div>
            </div>
          </li>
        );
      })}
    </ul>
  );
};

 


문제 해결 

 

(1)

 <img src = {tweet.picture}></img>  
 
  /*아래와 같이 에러가 뜬다*/
 🟣 해결 -> <img>   </img> 빈칸이 생기면 자식요소라고 판단하여 오류를 내므로 태그 사이 빈칸을 없앰

 

HTML elements such as <area />, <br />, and <input /> are void elements which are only self-closing without any content. Therefore, React will throw an exception if you set either children or dangerouslySetInnerHTML prop for a void element.

See

  • React Error: hr is a void element tag and must neither have children nor use dangerouslySetInnerHTML.
  • React Error: input is a void element tag and must neither have children nor use dangerouslySetInnerHTML.
  • React Error: img is a void element tag and must neither have children nor use dangerouslySetInnerHTML.           

Noncompliant Code Example 

import React from 'react';

export var example1 = <hr>Hello</hr>; // REACT_VOID_ELEMENT_WITH_CHILDREN alarm
export var example2 = <input children='Hello' />; // REACT_VOID_ELEMENT_WITH_CHILDREN alarm
export var example3 = <img dangerouslySetInnerHTML={{ __html: 'Hello' }} />; // REACT_VOID_ELEMENT_WITH_CHILDREN alarm

Compliant Code Example 

import React from 'react';

export var example1 = <hr />;
export var example2 = <input />;
export var example3 = <div dangerouslySetInnerHTML={{ __html: 'Hello' }} />;

 

(2)

 

 <i className="fa-brands fa-twitter fa-lg" style="color: #91b3ee;"></i>
 
 /*아래와 같이 에러가 뜬다*/
🟣 이렇게 해결할 수 있음 -> style={{color: 'blue'}}
🟣 한계 -> 16진 색상은 App.js에서 쓸 수 없는 듯 하다. (구글링에서도 'red', 'brown' 이런 식)
🟣 해결 -> CSS로 옮겼다.

에러 문구에 어떻게 하라고 친절히 안내가 되어있다 : {{}} 가장 밖의 {}는 JSX 문법, 안의 {}는 객체 리터럴이다. 

728x90
⬆︎