[React 완벽 가이드] Section 3: 리액트 핵심 (Components, JSX, Props, Event, useState)

gonn-i·2024년 5월 25일
0

React 완벽 가이드

목록 보기
1/18

본 포스트는 Udemy 리액트 완벽가이드 2024 를 듣고 정리한 내용입니다.

사실 section1-2도 정리했지만 날려먹음 이슈로 Section3부터 진짜 착실하게 다시 정리합니다


목차 🌳
1️⃣ Component
2️⃣ JSX
3️⃣ react의 구조
4️⃣ Props
5️⃣ Event
6️⃣ useState

📁 Component

Component 란, "HTML + CSS + JS" 코드 조각

Component (컴포넌트)란
코드 조각인데 이제, "HTML + CSS + 자바스크립트 로직"을 감싼 것

쉽게 조각이라고 생각하려고 한다.
복잡하고 큰 그림 (= 인터페이스) 를 조각조각 분리 (= 컴포넌트화)해서,
1) 관리하기 쉽고
2) UI의 다른 부분에서도 재사용할 수 있게 한 것

노란색 박스 영역이 바로, 같은 컴포넌트를 재사용하여, 내부의 데이터만을 바꾸어 여러번 재사용한 것

Component 왜 사용하나!

1️⃣ Reusable Building Blocks

예전엔 버튼이 필요한 대로 버튼 X N개 만들어야 했지만,
필요에 따라 컴포넌트 하나만 만들어서 재사용하면 된다는 말씀

+수정이 필요한 경우, 원본만 고쳐도 모두 한번에 적용되어 효율적

2️⃣ Related Code Lives Together

이전엔 html은 html 파일에, js는 js파일에 저장되어 분리되어, 번갈아 작업해야 하기에 번거로움이 있었음. 하지만, 리액트의 경우 컴포넌트는 (HTML + (CSS) + JS의 구조이기에 관련된 UI 조각을 함께 관리할 수 있음)

3️⃣ Separation of Concerns

각 컴포넌트마다 각기다른 기능을 담당해주어, 처리를 단순화
기능을 잘게 쪼개서 소화하니 더 편하다는 의미

➕ 컴포넌트를 사용하는 프론트엔드 프레임워크는 React 뿐만 아니라, Vue, Angular, Svelte 와 모바일 프레임워크인 Flutter에서도 찾아볼 수 있다.

Component 사용 방법

함수를 선언하는데, 인제 JSX를 반환하는 함수
(JSX -> 화면에 직접 보여주고 싶은 것(UI))

  1. 첫글자는 무조건 대문자 (나 컴포넌트요 ~ 표시)
    (+) 두단어 이상일땐, PascalCase로 작성
  2. ⭐️ 컴포넌트 함수는 JSX를 반환해야 함

왜 대문자로 써야하나? -> 왜냐면, 우리가 만든 custom Component와, 내장된 기본태그를 구분하기 위해서 (충돌 방지_ )
header -> html 헤더 태그
Header -> custom Component


📁 JSX

JSX는 자바스크립트 문법 확장자

(Javascript Syntax eXtension)
자바스크립트 문법 확장자로,
js 안에서 HTML 문법을 사용할 수 있게 해줌

근데 JSX는 브라우저에서 지원이 안 됨 -> (before) 브라우저 도달/ 개발 서버에서 변환되어 rending시 읽힐 수 있도록 변환 (그래서 HTML 처럼 보이지만 JS이다.)

<동적 값을 다루는 JSX ⭐️>

리액트가 재사용성면에서 장점을 가지는 만큼, 하드코딩으로 작성할 경우 Bad 👎
반복적으로 사용되는 경우
<p>hi</p> 가 아니라 <p>{greeting}</p> 이렇게 작성하기

📖 동적값을 다루기 위해서는 { } 중괄호를 사용하면 된다.

📁 index.js ➡️ index.html 흘러흘러

리액트는 단 하나의 페이지로 이루어져 있다 (SPA) 그래서 html 파일도 단 한개인데 ☝🏻

<!-- 모든 요소가 root를 id로 갖는 저 div 태그 안에 들어감 -->
  <body>
    <div id="root"></div>
    <script type="module" src="/src/index.jsx"></script>
  </body>

그래서 다음과 같이 html에 있는 root 안에, 모든 리액트 컴포넌트가 그려지게 된다.
(how?) --- > createRootrender메소드가 하나의 루트 컴포넌드를 랜더링 시킴.

import ReactDOM from "react-dom/client";

import App from "./App.jsx";
import "./index.css";

const entryPoint = document.getElementById("root");
ReactDOM.createRoot(entryPoint).render(<App />);

Component tree 🌳 => App component 안에, 자식 component 그 안에, 자식 component

JSX는 트리 모양의 코드 구조를 가짐!
위에선 App 컴포넌트를 랜더링했는데, 이 App 컴포넌트가 자식을 가지고,
또 그 자식이 자식을 가지는 식으로 component 계층구조가 만들어짐

우리가 보는 화면은 👀
모든 component 에서 나온 JSX를 합쳐서 만들어진 DOM 임
(각각의 컴포넌트를 직접 보는게 아니다! )


📁 컴포넌트끼리 데이터를 주고 받고 싶다면? props!

props = 컴포넌트에 설정된 "custom HTML attribute"

모든 부모 컴포넌트는 자식에게 props를 통해 정보나 데이터를 전달한다!
문자열뿐만 아니라, 숫자, 객체나, 배열, 함수 모두 전달 가능
프롭스가 있어서 react의 재사용성이 꽃피는거랍니다 ~

props 이해하기 (단계별 설명) ⚙️

1) 프롭스 설정 (부모) (custom HTML 속성 / 아래와 같이 설정됨)

<CoreConcept title="Components" description="The core UI building block" />

2) 객체로 병합

{
	title: 'Components',
    description: 'The core UI building block'
}

3) 프롭스 전달 (자식)
(첫번째 인자로 받음_ 대신 (2)에 따라 key-value 값으로 전달받음)

function CoreConcept(props) {
	return (
      <h3> {props.title} </h3>
    )
}

props 사용시, 깔끔한 코드를 위한 참고 사항 ✨

재사용으로, 전달할 props가 많을때 상수값들을 data.js에 넣어서 주면 아주 굿,
이때

<CoreConcept
title={CORE_CONCEPTS[2].title}
description={CORE_CONCEPTS[2].description}
img={CORE_CONCEPTS[2].image}/>

이런식으로 줘도 좋지만, props로 설정한 attribute와 data.js 안에 설정한 key 값이 같은 경우에는

<CoreConcept {...CORE_CONCEPTS[2]} />

이런식으로 spread operator 사용하는게 훨씬 깔끔하다!

또한, 매개변수에서 { } 중괄호를 추가해 구조분해를 활용하면 불필요한 코드의 반복을 줄일 수 있다.
(요랬는데)

function CoreConcept(props) {
  return (
    <li>
      <img src={props.image} alt={props.title}></img>
      <h3>{props.title}</h3>
      <p>{props.description}</p>
    </li>
  );
}

(요래~ 됐습니다)
그대신 props 안에 전달되었던, attribute의 이름그대로 써주기

function CoreConcept({ title, image, description }) {
  return (
    <li>
      <img src={image} alt={title}></img>
      <h3>{title}</h3>
      <p>{description}</p>
    </li>
  );
}

📁 childeren prop는 컴포넌트 사이에 전달되는 값!

열림태그와 닫힘태그 사이에 있는 내용을 자식 컴포넌트에게 전달하고 사용할 수 있게 한다.

부모.jsx

<TabButton> Components </TabButton>

컴포넌트 사이에 위와 같이 텍스트가 전달되거나, 숫자, 혹은 JSX가 자식에게로 보내진다.

자식.jsx

export default function TabButton(props) {
  return (
    <li>
      <button>{props.children}</button>
    </li>
  );
}

attribute props vs children prop

attribut Props여러개의 정보(prop)를 전달할때에 유용

<TabButton label="Components" />

반면, children Props하나의 정보를 전달하거나, JSX 를 값으로 넘길때 유용

components 파일안에서 개별 컴포넌트 관리하기

App.jsx에 여러 컴포넌트 생성해서 같이 관리 ❌

└─ src
├─ components ⭐️
├─ Headers.jsx
├─ Footer.jsx
├─ assets
├─ App.js
└─ index.js

(파일 이름) == (컴포넌트 이름)과 같게 설정 -- 그러면 당연히 대문자로 시작해야함)

추가로, 컴포넌트 옆에 컴포넌트 스타일 (css) 파일을 저장하고, 그 둘을 묶어 하나의 파일로 📁 만드는 방법은 가독성 귯


Event Linstener도 props를 통해 📤

Event Linstener 는 react 안에서, prop를 통해 내장된 컴포넌트에 전달된다.
Event Handler의 경우, 이벤트 리스너 prop의 값에 전달하면 된다.

이때, 함수 전달시 괄호를 추가해서 넘겨주면 안된다.
<button onClick={handleClick()}> 오직 함수의 이름만을 넘겨줘야 하는데,
그 이유는 괄호 추가시 코드 라인 실행시, 함수가 바로 실행되기 때문.
그래서 버튼이 클릭되면 리액트에 의해 실행될 수 있도록 단순히 함수 이름만(포인터)을 값으로 전달

function TabButton(props) {
  function handleClick() {
    console.log('hi');
  }
  
  return (
    <li>
      <button onClick={handleClick}>{props.children}</button>
    </li>
  );
}

Event Handler에 custom 인자도 넘겨줄 수 있음

어떤 탭을 클릭하냐에 따라 다른 설명이 나오는 화면을 만들때,
클릭이라는 이벤트에 따라 각각 탭(버튼)이 출력하는 화면이 다른 경우에, custom 인자를 전달해 이들을 구분할 수 있다.

<TabButton onSelect={() => handleSelect('component')}>Component</TabButton>
<TabButton onSelect={() => handleSelect('jsx')}>JSX</TabButton>
<TabButton onSelect={() => handleSelect('props')}>Props</TabButton>
<TabButton onSelect={() => handleSelect('state')}>State</TabButton>

위 처럼 인자를 전달할 때는 함수를 다른 함수로 감싸면 됨
onClick={() => handleClick(5)}
arrow function을 이용하고, custom 인자를 담아서 넘긴다.

function handleSelect(selectdButton) {
  console.log(selectdButton);
}

그럼 이렇게 매개변수를 받아서,
이걸로 버튼을 구분하여 이에 맞는 설명글을 보여줄 수 있겠다!

📁 useState (상태관리 도구)

왜 사용하나?

이벤트 핸들러 함수를 통해서 동적으로 할당된 값을 토대로, UI를 업데이트하려고 한 당신, 하지만 코드는 동작하지 않을 것이다. (변수만 업데이트, UI 업데이트 ❌)

왜냐하면, 컴포넌트 함수가 1번만 실행되기 때문에, UI가 없데이트 되지 않는 것인데.
기본적으로 리액트는 컴포넌트 함수를 코드에서 처음 발견했을때, 한번만 실행(랜더링)하기 때문이다. 초기 UI와 바뀐 UI를 비교해야 하는데, 랜더링이 한번만 되니 초기값만 알게 되는 상황이다.

때문에, 일반적인 변수로는 UI를 업데이트 할 수 없고,
변화가 일어났을때 컴포넌트를 다시 랜더링할 수 있도록 알려줘야 함

그래서 useState가 뭔데

리액트에서 변수를 등록하는 방법인데,
인제 변수가 변할때마다, 자신이 속한 컴포넌트 함수를 활성화하여 UI를 업데이트 해준다. (re-render 시켜준다는 말)
리액트에서 use 로 시작하는 모든 함수는 리액트 Hook 인데, 마찬가지로 useState도 hook 이다

🥸 hook 함수 사용 방법

1️⃣ Hook 함수는 컴포넌트 안에서 호출되어야 함.

// ⭕️ 옳게 쓴 상황 ⭕️
function App () {
	const [val, setVal] = useState(0);
}
// ❌ 그르게 쓴 상황 ❌
const [val, setVal] = useState(0);
function App () { ... }

2️⃣ Hook 함수는 최상위에서 호출되어야 함 (중첩 금지)

// ⭕️ 옳게 쓴 상황 ⭕️
function App () {
	const [val, setVal] = useState(0);
}
// ❌ 그르게 쓴 상황 ❌
function App () {
	if(someCondition) {
    	const [val, setVal] = useState(0);
    }
}

useState 선언하는 방법

1) import 해오기 import { useState } from 'react'
2) 컴포넌트 안에서, 최상위 레벨에서 선언
3) 선언 형식 let [현재상태변수, 상태변경함수] = useState(초기값)

😎 useState로 데이터 값 가져오는 방법

data.js에 상태값에 대해 객체로 내용을 선언하면 쉽게 내용을 출력할 수 있다!

data.js


EXAMPLES = {
  components: {
    title: 'Components',
    description:
      'Components are the building blocks of React applications. A component is a self-contained module (HTML + optional CSS + JS) that renders some output.',
    code: `function Welcome() {return <h1>Hello, World!</h1>;}`,
  }, ... 
}

App.js

<div id="tab-content">
  <h3>{EXAMPLES[selectdTopic].title}</h3>
  <p>{EXAMPLES[selectdTopic].description}</p>
  <pre>
    <code>{EXAMPLES[selectdTopic].code}</code>
  </pre>
</div>

🖼️ 동적 스타일링 with useState

버튼이 눌렸는지에 따라서, className인 active 를 주어 css 효과를 주고 싶다면 ?!

본래 jsx에서는 if문이나 삼항연산자를 사용할 수 없는데, 이때 { } 중괄호를 쓰면 이야기가 달라진다! {} 중괄호 안에 삼항연산자를 넣거나 비교문을 넣어서 상태값의 변화에 따라서 동적으로 작동한다

부모.jsx

<TabButton isSelected={selectdTopic === 'components'} onSelect={() => handleSelect('components')}>
  Component
</TabButton>

자식.jsx

<button className={isSelected ? 'active' : undefined} onClick={onSelect}>
  {children}
</button>

🧐 동적으로 List 출력하기

이전코드
(P) 만약 data.js에서 CORE_CONCEPTS 배열에서 값이 삭제되면 오류가 발생하고,
값이 추가될 경우 연쇄적으로 jsx의 코드에 수정이 필요했다.

<CoreConcept {...CORE_CONCEPTS[0]} />
<CoreConcept {...CORE_CONCEPTS[1]} />
<CoreConcept {...CORE_CONCEPTS[2]} />
<CoreConcept {...CORE_CONCEPTS[3]} />

동적인 코드

map 함수를 통해, 배열을 차례로 돌면서 출력하기 때문에 동적인 아주 좋은 코드!

{CORE_CONCEPTS.map((conceptItem) => (
    <CoreConcept key={conceptItem.title} {...conceptItem} />
))}

0개의 댓글