본문 바로가기
  • 기록
React/(Udemy)React-The Complete Guide

[React] 컴포넌트 실습(섹션3-2)

by juserh 2022. 4. 15.
<Component Tree>
리액트는 컴포넌트 트리 구조를 가진다. root component는 <App/>, 브라우저에 직접 렌더링되는 single HTML page이다. 이 외 다른 컴포넌트들은 또 다른 컴포넌트나 루트 컴포넌트에 포함되는 구조.

 

1. 컴포넌트 기본 사용 로직, 방법

컴포넌트를 생성해서 사용하는 방법은 간단히 3단계이다.

1. 컴포넌트 function으로 html코드 생성   2. export, import   3. import해온 컴포넌트 이용(JSX syntax)

 

// components/ExpenseItem.js
function ExpenseItem() {
  return <h2>Expense Item!</h2>;
}

export default ExpenseItem;

 

import ExpenseItem from "./components/ExpenseItem";

function App() {
  return (
    <div className="App">
      <h1>Let's get started!</h1>
      <ExpenseItem></ExpenseItem>
    </div>
  );
}

export default App;

 


2. 조금 더 복잡한 컴포넌트

<리액트 컴포넌트 생성 시 주의점>
각 컴포넌트는 반드시 하나의 루트 요소를 가져야 한다.
function ExpenseItem() {
  return (
    <div>Date</div>
    <div><h2>Title</h2><div>Amount</div></div>);
}

export default ExpenseItem;

예를 들어 위와 같이 코드를 작성하면 빨간줄이 뜬다. 반환되는 html 요소들을 하나로 아우르는 루트 요소가 없기 때문이다.

빨간줄

function ExpenseItem() {
  return (
    <div>
      <div>Date</div>
      <div>
        <h2>Title</h2>
        <div>Amount</div>
      </div>
    </div>
  );
}

export default ExpenseItem;

<div>태그를 추가해 전체 내용을 감싸주는 방식으로 수정하여 하나의 root element를 만들어주었다.

 

 


3. CSS 스타일 추가

1. css 파일 작성   2. 적용할 css파일을 js파일에서 import

<css 적용 시에 주의점>
리액트 JSX 구문은 html과 비슷해 보이긴 하지만 그래도 여전히 자바스크립트이다. 따라서 html과 다른 부분이 있다. html에서 특정 element들에 대해 class="어쩌구"로 클래스 이름을 정의할 수 있는데 JSX 구문에서는 이를 className="어쩌구"로 정의한다. 

 


4. 동적 데이터 출력 및 표현식 작업

이전까지는 컴포넌트 html코드에 값을 hard coding해서 보여줬는데 dynamic placeholder를 이용해 동적으로 바꿀 수 있다.

import "./ExpenseItem.css";

function ExpenseItem() {
  const expenseDate = new Date(2022, 3, 15);
  const expenseTitle = "Car insurance";
  const expenseAmount = 294.67;

  return (
    <div className="expense-item">
      <div>{expenseDate.toISOString()}</div>
      <div className="expense-item__description">
        <h2>{expenseTitle}</h2>
        <div className="expense-item__price">${expenseAmount}</div>
      </div>
    </div>
  );
}

export default ExpenseItem;

return문의 html코드에 존재하는 {} 안에서는 자바스크립트 구문이 실행될 수 있다. 비록 현재는 return문 앞에 자바스크립트 코드로 constant(상수)로 값을 저장해놓긴 했지만...여하튼 {}를 이용해 http request로 받아온 값들을 동적으로 컴포넌트에 보여줄 수 있다는 것.

 


5. props로 데이터 전달

make reusable component by using parameter("props" in react)

매개변수를 통해서 app(즉, 우리가 만든 컴포넌트 외부 파일)에서 컴포넌트로 데이터를 전달하여 truly reusable(재사용 with 다른 데이터)하게 사용할 수 있다. 컴포넌트에 attribute을 추가해서 props(properties)를 설정하면 된다.

리액트에서는 하나의 매개변수로 모든 속성을 받을 수 있다. props 객체는 key와 value의 쌍을 갖는다. key는 attribute(속성)의 이름, value는 그 값을 말한다.

 

바로 앞서 만든 ExpenseItem 컴포넌트를 진짜 재사용가능하게 만들려면, 외부 파일(여기서는 app.js)에서 데이터를 전달하여 해당 컴포넌트를 이용하여 여러 아이템이 보여질 수 있도록 해야한다. 

import ExpenseItem from "./components/ExpenseItem";

function App() {
  const expenses = [
    {
      id: "e1",
      title: "Toilet Paper",
      amount: 94.12,
      date: new Date(2020, 7, 14),
    },
    { id: "e2", title: "New TV", amount: 799.49, date: new Date(2021, 2, 12) },
    {
      id: "e3",
      title: "Car Insurance",
      amount: 294.67,
      date: new Date(2021, 2, 28),
    },
    {
      id: "e4",
      title: "New Desk (Wooden)",
      amount: 450,
      date: new Date(2021, 5, 12),
    },
  ];
  return (
    <div className="App">
      <h1>Let's get started!</h1>
      <ExpenseItem
        title={expenses[0].title}
        amount={expenses[0].amount}
        date={expenses[0].date}
      ></ExpenseItem>
      <ExpenseItem
        title={expenses[1].title}
        amount={expenses[1].amount}
        date={expenses[1].date}
      ></ExpenseItem>
      <ExpenseItem
        title={expenses[2].title}
        amount={expenses[2].amount}
        date={expenses[2].date}
      ></ExpenseItem>
      <ExpenseItem
        title={expenses[3].title}
        amount={expenses[3].amount}
        date={expenses[3].date}
      ></ExpenseItem>
    </div>
  );
}

export default App;

전에는 ExpenseItem.js파일에서 값을 지정하여 넣어주었지만, 이번에는 외부 app.js파일에서 데이터 배열을 만들어 그 값을 ExpenseItem에 속성으로 전달하여 보여지게 수정하였다. {}을 이용하여 배열에 접근하여 컴포넌트에 값을 전달한다.

 

import "./ExpenseItem.css";

function ExpenseItem(props) {
  return (
    <div className="expense-item">
      <div>{props.date.toISOString()}</div>
      <div className="expense-item__description">
        <h2>{props.title}</h2>
        <div className="expense-item__price">${props.amount}</div>
      </div>
    </div>
  );
}

export default ExpenseItem;

값을 전달받으려면 매개변수를 이용해야한다. props로 컴포넌트로 전달된 모든 attributes를 받을 수 있다. props객체로 받은 데이터에 접근하여 {props.date}, {props.title}, {props.amount}로 값이 보여질 수 있도록 한다.

그럼 이렇게 하나의 컴포넌트에 다른 데이터를 넣어서 여러 아이템이 보인다!


6. 컴포넌트에 일반 자바스크립트 구문 추가

위 브라우저 결과를 보면 날짜가 아주 보기 보기 불편해서 자바스크립트 구문을 넣어 읽기 편하게 바꾸어보려고 한다.
import "./ExpenseItem.css";

function ExpenseItem(props) {
  const month = props.date.toLocaleString("en-US", { month: "long" });
  const day = props.date.toLocaleString("en-US", { day: "2-digit" });
  const year = props.date.getFullYear();

  return (
    <div className="expense-item">
      <div>
        <div>{month}</div>
        <div>{year}</div>
        <div>{day}</div>
      </div>
      <div className="expense-item__description">
        <h2>{props.title}</h2>
        <div className="expense-item__price">${props.amount}</div>
      </div>
    </div>
  );
}

export default ExpenseItem;

return문 전에 자바스크립트 구문을 넣어 props객체로 받아서 month, day, year를 읽기 편하게 변환했다.

 

위 사진과 같이 날짜가 읽기 편하게 바뀌었다.

 


7. 컴포넌트 분할

컴포넌트 코드를 수정하고 추가하면서 컴포넌트 내용이 점점 불어나고 있다. 그래서 이를 분할해보려고 한다. 컴포넌트를 적절한 단위, 크기로 분할하면 컴포넌트를 더 쉽게 다루고 조작하는 동시에, 여전히 복잡한 유저 인터페이스를 구성할 수 있다.

 

ExpenseItem.js 파일에서 날짜 부분을 또 다른 컴포넌트(ExpenseDate.js)로 분할하려고 한다.

import React from "react";
import "./ExpenseDate.css";

function ExpenseDate(props) {
  const month = props.date.toLocaleString("en-US", { month: "long" });
  const day = props.date.toLocaleString("en-US", { day: "2-digit" });
  const year = props.date.getFullYear();

  return (
    <div className="expense-date">
      <div className="expense-date__month">{month}</div>
      <div className="expense-date__year">{year}</div>
      <div className="expense-date__day">{day}</div>
    </div>
  );
}

export default ExpenseDate;

 

import "./ExpenseItem.css";
import ExpenseDate from "./ExpenseDate";

function ExpenseItem(props) {
  return (
    <div className="expense-item">
      <ExpenseDate date={props.date} />
      <div className="expense-item__description">
        <h2>{props.title}</h2>
        <div className="expense-item__price">${props.amount}</div>
      </div>
    </div>
  );
}

export default ExpenseItem;

본래 ExpenseItem.js 파일에 있던 날짜부분을 분할하여 ExpenseDate.js 파일에 담아 ExpenseDate 컴포넌트를 만들어 ExpenseItem 컴포넌트에서 import 하여 사용했다.

이때 주의할 점이 있는데, 본래 app.js에서 받아온 props에 담긴 date를 ExpenseDate 컴포넌트까지 전달해주어야 한다는 것이다. 즉, 날짜 정보가 app.js에서 ExpenseItem 컴포넌트를 거쳐(통해서) ExpenseDate 컴포넌트까지 가야한다. 때문에

ExpenseItem.js 코드를 살펴보면

<ExpenseDate date={props.date} />

이 코드를 통해 ExpenseItem 컴포넌트가 app.js에서 받은 날짜 정보를 ExpenseDate 컴포넌트로 넘겨준다.

 

추가적으로 app.js에 있는 ExpenseItem 컴포넌트들을 모아놓은 Expenses 컴포넌트를 만들었다. 이 경우에는 app.js에 존잰하는 expenses 배열을 Expenses 컴포넌트로 넘겨주어야 한다.

import React from "react";
import ExpenseItem from "./ExpenseItem";
import "./Expenses.css";

function Expenses(props) {
  return (
    <div className="expenses">
      <ExpenseItem
        title={props.items[0].title}
        amount={props.items[0].amount}
        date={props.items[0].date}
      />
      <ExpenseItem
        title={props.items[1].title}
        amount={props.items[1].amount}
        date={props.items[1].date}
      />
      <ExpenseItem
        title={props.items[2].title}
        amount={props.items[2].amount}
        date={props.items[2].date}
      />
      <ExpenseItem
        title={props.items[3].title}
        amount={props.items[3].amount}
        date={props.items[3].date}
      />
    </div>
  );
}

export default Expenses;

 

import Expenses from "./components/Expenses";

function App() {
  const expenses = [
    {
      id: "e1",
      title: "Toilet Paper",
      amount: 94.12,
      date: new Date(2020, 7, 14),
    },
    { id: "e2", title: "New TV", amount: 799.49, date: new Date(2021, 2, 12) },
    {
      id: "e3",
      title: "Car Insurance",
      amount: 294.67,
      date: new Date(2021, 2, 28),
    },
    {
      id: "e4",
      title: "New Desk (Wooden)",
      amount: 450,
      date: new Date(2021, 5, 12),
    },
  ];
  return (
    <div className="App">
      <h1>Let's get started!</h1>
      <Expenses items={expenses} />
    </div>
  );
}

export default App;

app.js 코드가 훨씬 간단해진 것을 볼 수 있다. 그리고 Expenses 컴포넌트에 css스타일까지 적용해주면...! 아래와 같은 모습을 확인할 수 있다.

 

 

+)여기서 component tree를 확인해보면 아래와 같다(동그라미는 매개변수, 전달되는 데이터)

 


8. 컴포지션(Composition)

Expenses 컴포넌트와 ExpenseItem 컴포넌트에 대해 동일하게 적용된 css스타일 속성 몇 가지를 추출하여 래퍼 컴포넌트를 만들어 사용하려고 한다.

.card {
  border-radius: 12px;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
}

 

import React from "react";
import "./Card.css";

function Card(props) {
  const classes = "card " + props.className;

  return <div className={classes}>{props.children}</div>;
}

export default Card;

Card 컴포넌트는 래퍼 컴포넌트로 다른 html 요소들을 내용으로 담을 수 있는 컴포넌트이다. 이런 래퍼 컴포넌트는 props를 사용하여야 한다. props.childeren은 컴포넌트 매개변수 props의 이미 존재하는 예약어?키워드?로 컴포넌트 안에 들어있는 다른 html요소들을 담고 있다. 따라서 이를 이용해 래퍼 컴포넌트를 생성할 수 있다. 

두 컴포넌트에 동일하게 적용된 css코드를 래퍼 컴포넌트를 생성함으로써 코드 중복을 줄일 수 있다.

 

+위 코드를 살펴보면 classes 상수를 'card '와 props로 전달된 className를 합쳐('card ' 빈칸 없이 더하면 안됨...!) 선언했는데 이 이유는 본래 className으로 css 스타일을 적용했던 것이 Card 컴포넌트 사용한 후에도 계속 적용되도록 하기 위함이다.

 

컴포지션은 리액트에서 매우 중요하다. 컴포넌트 결합이 컴포지션!

 


9. JSX

a special, non-standard syntax which is enabled in React projects

리액트는 JSX코드를 지원하지만, 브라우저는 JSX코드를 지원하고있지 않다. 리액트가 JSX코드를 브라우저에서 사용가능하게 변환해준다.

브라우저 개발자모드로 js코드 확인해보면 우리가 작성한 코드와 다른 것을 볼 수 있다.

//app.js의 리턴문
return (
    <div className="App">
      <h1>Let's get started!</h1>
      <Expenses items={expenses} />
    </div>
  );

위 JSX코드를 리액트 모듈을 사용하여 표현하면,

return React.createElement(
    'div',
    {},
    React.createElement('h2', {}, "Lets get started!"),
    React.createElement(Expenses, {items: expenses})
);

이렇게 된다. 우리가 JSX코드로 쓴 것을 이렇게 자동으로 변환해준다.

 


10. 컴포넌트 파일 구성

컴포넌트 파일들을 깔끔하게 구성하는 것도 중요하다.

이번 프로젝트의 경우, 유저인터페이스와 특정 기능이나 역할이 있는 컴포넌트를 구분하여 UI와 Expense폴더로 컴포넌트 파일을 조직하였다.

 


11. alternative syntax

import React from "react";
import "./Card.css";

const Card = (props) => {
  const classes = "card " + props.className;

  return <div className={classes}>{props.children}</div>;
}

export default Card;

화살표 함수로 표현해도 괜찮다. 특별히 더 좋은 것이라고 말할 순 없고, 그냥 개인 취향이라고 한다.