221124.til

Universe·2022년 11월 23일
0

study

목록 보기
46/49
post-custom-banner

컴포넌트(Components)란 ?

컴포넌트란 무엇이고 왜 리액트에서 중요한 개념일까 ?

모든 사용자 인터페이스는 모두 컴포넌트로 구성되어있다.

예를들어, 같은 아이템인데 다른 데이터 값을 가지고 있다면 컴포넌트로 구성하는 것이 바람직하다.

재사용할 수 있는 빌딩블럭이라는 뜻.

결국 컴포넌트는 HTML / CSS 그리고 자바스크립트의 결합이다.

재사용이 가능하고 유지보수가 쉽다는점이 함수와 비슷하다.

각각의 컴포넌트가 하나의 명확한 기능에 초점을 맞출 수 있도록 구현하는 것이다.

리액트에서는 항상 원하는 최종 상태, 목표상태 또는 다양한 상황에 따른 다른 목표 상태를 정의한다.

그리고 리액트는 실제 웹 페이지에서 어떤 요소가 추가되고, 삭제되고, 업데이트 되어야 하는지를 해결한다.

자신만의 html 태그를 만들고, 그를 정의하여 재사용 하는 데 용이하게 한다.

리액트 프로젝트 생성

npx create-react-app 작명 옵션
npm start

localhost:3000 에서 확인할 수 있다.

옵션에 typescript 같은 속성을 추가하면 typescript 로 리액트 프로젝트를 만들 수 있다.

npx 는 해당 프로그램을 임시로 설치하고 지우는 작업이므로

항상 최신버전을 유지할 수 있고 저장공간을 절약할 수 있다.

SPA

react 환경에서 다루는 HTML 파일은 기본적으로 public 폴더의 index.html 하나이다.

index.html 에는

<div id="root"></div>

body태그 안에 root 라는 id를 가진 div 하나를 볼 수 있는데,

index.js 파일에

const root = ReactDOM.createRoot(document.getElementsById)('root'));
root.render(<App />)

메인 리액트 app이 렌더링 되는 위치를 지정해준 것인데,

render( 는 root라는 id를 가진 요소의 자리에 렌더링 할 컴포넌트 라는 의미가 된다.

App.js 파일을 살펴보면,

function App() {
  return (
    <div className="App">
    </div>
  );
}

export default App;

이런식으로 구성된 함수가 있는데,

JSX 형식으로 구성된 return 을 export default 로 반환한다.

JSX 는 React 팀에서 구성한 특별한 자바스크립트 구문인데,

자바스크립트 파일에서 HTML 형식의 구문을 사용할 수 있게 해주는 역할을 한다.

JSX

리액트 프로젝트를 크롬개발자도구 → sources 탭에서 확인해보면

코드에디터에서 작성한 파일이 조금 달라진 것을 확인할 수 있는데,

JSX방식으로 작성한 코드들이 React, React-Dom 기능의 역할로 변환되는 것을 볼 수 있다.

브라우저에서는 JSX방식으로 작성된 코드를 읽을 수 없으므로

라이브러리 기능으로 자동변환 과정을 거쳐 번역해주는 작업이다.

리액트 작동 방식

예를들어 바닐라 자바스크립트 에서는 버튼요소를 dom에 추가할 때,

const par = document.createElement('p')
par.textContent = 'Hello World'
document.getElementById('root').appendChild(par)

이런식으로 ‘명령형’ 으로 작성한다.

위 코드는 간단한 p 태그를 추가하는 로직이지만,

조금 더 복잡하고 유기적으로 데이터의 변환이 일어나는 로직이라면

코드가 훨씬 길어질 것이다.

하지만 리액트에서는

function App(){
	return (
		<p> Hello Wolrd </p>
	)
}

이런식으로 간단하게 작성할 수 있다.

리액트는 이 방법에서 크게 벗어나지 않는다.

작은 단위로 설계한 컴포넌트들에 조금 더 편리한 특정 Hook 들을 이용해 제어하는 것이다.

첫번째 컴포넌트 만들기

컴포넌트를 분리하는 방식으로 연습하는 것이 좋다.

현업에서 컴포넌트 단위로 분리한 js 파일들을 여러개 만드는 것이 지극히 정상적이기 때문이다.

src 디렉토리에 components 디렉토리를 생성하고

그 안에 여러 컴포넌트들을 설계할 것이다.

이는 App.js를 ‘루트 컴포넌트’ 로 두고 여러 컴포넌트들을 App 컴포넌트에 병합하는 과정인데,

재사용과 유지보수의 관점에서 유리하다.

컴포넌트 트리 라고 하는데

이런식으로 A 라는 루트 노드 (리액트의 경우 컴포넌트) 에

하위 노드 (하위 컴포넌트) 를 추가하는 방식이다.

컴포넌트 설계

리액트의 컴포넌트는 다른게 아니라 하나의 함수일뿐이다.

components 폴더에 ExpenseItem.js 라는 파일을 만들고

const ExpenseItem = () => {
  return <h2>Expense Item !</h2>;
};

export default ExpenseItem;

함수를 설계한다.

ExpenseItem 이라는 함수는 <h2> 태그를 리턴하는 함수이다.

해당 함수를 다른 파일에서 쓰기위해서 export 해주는 과정

App.js에서

import ExpenseItem from './components/ExpenseItem';

function App(){
	return (
		<ExpenseItem />
	)
}

import 하면 해당 함수를 마치 HTML 태그처럼 사용할 수 있다.

컴포넌트는 내장 HTML와 구분하기 위해 대문자로 시작하는 것이 원칙이다.

더 복잡한 JSX

const ExpenseItem = () => {
  return (
    <div>Date</div>
    <div>
      <h2>title</h2>
    </div>
  );
};

JSX 는 중요한 규칙이 있는데,

return 하는 요소는 단 하나의 루트 요소를 갖는다는 것이다.

2개 이상의 div를 허용하지 않는 것이다.

이를 해결하기 위해서, 큰 div 로 묶는 방법이 있다. 이 방법이 제일 간단하다.

JSX → CSS

리액트에서도 CSS 를 사용한다.

CSS 파일을 생성하고,

HTML 태그에 class 를 추가해, 각 태그의 속성을 선택하여 스타일링을 하던 방식 그대로

JSX 에서도 할 수 있다.

const ExpenseItem = () => {
  return (
    <div className="items">
      <div className="item-wrapper">
        <h2 className="item-title">Car</h2>
        <div className="item-price">$294.64</div>
      </div>
    </div>
  );
};

JSX 에서는 class 가 className 라는 키워드로 바꾸어 사용해야 한다.

위에서 export 한 css 파일을 스타일링 하면 스타일을 적용시킬 수 있다.

동적으로 데이터 할당하기

여러개의 데이터를 받아 동적으로 원하는 태그에 할당하려면 어떻게 해야하는지 알아보자.

const ExpenseItem = () => {
const expenseDate = new Date()
const expenseTitle = 'Car'
const expensePrice = '294.64'
  return (
    <div className="items">
      <div className="item-wrapper">
        <h2 className="item-title">Car</h2>
        <div className="item-price">$294.64</div>
      </div>
    </div>
  );
};

해당 함수를 return 하기 전에 자바스크립트 코드를 작성할 수 있다.

함수 내에 선언한 변수를

const ExpenseItem = () => {
const expenseDate = new Date()
const expenseTitle = 'Car'
const expensePrice = '294.64'
  return (
    <div className="items">
			<div>{expenseDate}</div>
      <div className="item-wrapper">
        <h2 className="item-title">{expenseTitle}</h2>
        <div className="item-price">{expensePrice}</div>
      </div>
    </div>
  );
};

중괄호로 JSX return 내에 동적으로 할당해줄 수 있다.

그런데 위의 코드를 실행시키면 제대로 작동하지 않는데,

그 이유는 new Date() 는 객체형태의 데이터라서 그렇다.

<div>{expenseDate.toDateString()}</div>

Date 의 메소드 toDateString()나 toISOString() 등을 사용하면 제대로 출력된다.

props를 사용한 재사용이 용이한 컴포넌트 만들기

위에서 만든 컴포넌트는 재사용이 불가능하다.

데이터를 변수에 담아 할당하긴 했지만 여전히 데이터가 하드코딩 되어있기 때문이다.

함수에는 재사용을 위해 파라미터를 사용해 구멍을 뚫고 그 구멍에 원하는 데이터를 알맞게 가공할 수 있다.

리액트의 컴포넌트에서도 가능하다.

const ExpenseItem = (props) => {
 return (
    <div className="items">
			<div>{props.item}</div>
      <div className="item-wrapper">
        <h2 className="item-title">{props.title}</h2>
        <div className="item-price">{props.price}</div>
      </div>
    </div>
  );};

재사용 할 컴포넌트에서 props를 정의해준다.

마치 함수에 파라미터를 부여하는 것과 같은데, 해당 props 키워드는

원하는 대로 작명해도 상관없다.

객체에서 자료를 뽑아오는 것 처럼 props.키워드 로 원하는 자료를 바인딩한다.

그 후 루트 컴포넌트에서

const items = {
	item : 'item A',
	title : 'title A',
	price : 10000
}
...
<ExpenseItem
        item={items[0].item}
        title={items[0].title}
        price={items[0].price}
      />

props에 바인딩할 key 값을 정의해주면 된다.

컴포넌트 분할하기

이전에 컴포넌트 트리 구조를 봤다.

컴포넌트내의 코드가 길어져 세분화 하고싶을 경우가 있을 수 있는데,

이런 경우 컴포넌트의 자식 노드를 구성하면 된다.

컴포넌트의 자식을 만드는 규칙은 없다. 자유롭게 세분화 할 수 있는데,

강의의 저자는 기능을 담당하는 div 단위로 구분하는 듯 하다.

컴포넌트를 분할하는 일은 어렵지 않다.

App 컴포넌트에서 자식노드를 생성할 때 처럼,

컴포넌트에서 자식 컴포넌트를 import 해오고

컴포넌트 이름을 불러주면 되는 것이다.

// App.js
...
<ExpenseItem
        title={expenses[0].title}
        amount={expenses[0].amount}
        date={expenses[0].date}
      />
...
// ExpenseItem 컴포넌트에 expense 객체의 데이터를 명시된 키에 담아 전송

// ExpenseItem.js (자식 컴포넌트)
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>
  );
// 전달받은 객체의 데이터를 props 로 받아 사용, 자식 컴포넌트로 명시된 키에 담아 전송

// ExpenseDate.js (자식 컴포넌트의 자식 컴포넌트)
const ExpenseDate = (props) => {
  const month = props.date.toLocaleString("ko", { month: "long" });
  const day = props.date.toLocaleString("ko", { day: "2-digit" });
  const year = props.date.getFullYear();
  return (
    <div className="expense-date">
      <div className="expense-date__year">{year}</div>
      <div className="expense-date__month">{month}</div>
      <div className="expense-date__day">{day}</div>
    </div>
  );
};
// 부모 컴포넌트로 부터 받은 props를 이용해 정보를 가공하고 변수에 담아 태그를 리턴

위 코드를 보면 props 로 받아온 데이터를 다시 props 로 전달해주는 것을 볼 수 있다.

지금 단계에서는 이 props 의 중첩을 대체할 다른 방법은 없다.

나중에 Redux 같은 상태관리 라이브러리로 이를 해소할 수 있다고 한다.

JSX 의 반복문

컴포넌트를 또 분리해보자.

App.js 파일에 Expense 를 여러개 그리는 과정을 하나의 컴포넌트로 묶어볼 수 있다.

컴포넌트는 세분화 하는 방법도 있지만 여러개의 컴포넌트를 하나로 묶을수도 있다.

// App.js
return (
    <div className="App">
      <Expense items={expenses} />
    </div>
  );

// expense 데이터를 새로 만든 컴포넌트 Expense 에 props로 할당. key는 items

// Expense.js
const Expense = (props) => {
  const expenses = props.items.map((e, n) => (
    <ExpenseItem key={n} title={e.title} amount={e.amount} date={e.date} />
  ));
  return <div className="expenses">{expenses}</div>;
};

전달받은 props 를 콘솔에 찍어보면 items 라는 key를 가진 객체가 나온다.

해당 객체의 items 는 객체를 원소로 갖는 배열임으로 map 반복문으로 해당 배열의 갯수만큼

HTML 태그를 생성하는 로직이다.

JSX 에서는 기본적인 for 반복문을 쓸 수 없다.

따라서 map 같은 배열 메소드를 이용해

const Expense = (props) => {
  const expenses = props.items.map((e, n) => (
    <ExpenseItem key={n} title={e.title} amount={e.amount} date={e.date} />
  ));

  return <div className="expenses">{expenses}</div>;
};

이런식으로 작성해주었다.

이 부분에서 key 라는 값을 바인딩 해주는 이유는

Each child in a list should have a unique "key" prop.

이러한 오류가 출력되기 때문이다.

해석하자면 많은 요소들이 식별가능한 key 를 가지고 있지 않다는 뜻인데,

이러한 배열의 key는 고유성을 부여해주는 역할을 한다.

생성되는 요소에 고유성을 부여해주기 위해 key를 내부에 지정해주는 것이다.

위의 경우에는 index로 key를 할당해주었지만,

고유성을 식별할 수 있는 수단이라면 어느것이어도 상관없다.

보통 데이터의 ID를 key로 사용하는것이 일반적이라고 한다.

profile
Always, we are friend 🧡
post-custom-banner

0개의 댓글