JAN's History

React 학습하기 - UI 표현하기 본문

React

React 학습하기 - UI 표현하기

JANNNNNN 2025. 7. 26. 22:08

1. 첫 번째 컴포넌트

컴포넌트란?

  • UI를 구성하는 재사용 가능한 단위
  • HTML, CSS, JavaScript를 하나의 함수 형태로 묶은 것
  • 마치 <div>를 쓰듯이, <MyComponent />형태로 사용

컴포넌트 정의하고 사용하기

React는 트리구조로 구성되어 하나의 부모 컴포넌트 안에 여러 자식 컴포넌트를 가질 수 있다.

function App() {
  return (
    <Layout>
      <Header />
      <Main>
        <Sidebar />
        <Content />
      </Main>
      <Footer />
    </Layout>
  );
}
 
//❌ 컴포넌트 안에 컴포넌트를 정의하지 말기
//➡️ 이렇게 하면 매번 Gallery가 렌더링될 때마다 Profile 함수도 새로 만들어져서 불필요한 리렌더링, 성능 저하가 발생
function Gallery() {
  function Profile() {
    return <img src="..." />;
  }
  return <Profile />;
}
 
 
// ✅ 이렇게 바깥에 선언!
function Profile() {
  return <img src="..." />;
}
function Gallery() {
  return <Profile />;
}

2. 컴포넌트 Import 및 Export하기

React 컴포넌트의 가장 큰 장점은 재사용성이다. 

하나의 컴포넌트를 여러 곳에서 사용하려면 다른 파일로 분리하고, import/export를 통해 가져와야한다.

// App.js
function Profile() {
  return <img src="..." alt="..." />;
}
//Gallery가 루트 컴포넌트, Profile가 하위 컴포넌트
export default function Gallery() {
  return (
    <section>
      <h1>Amazing scientists</h1>
      <Profile />
      <Profile />
    </section>
  );
}

Export 하기

default는 1개만, named는 여러 개 export 가능!

✅ Default Export
// Gallery.js
export default function Gallery() {
  return <div>Gallery</div>;
}
 
✅ Named Export
// Profile.js
export function Profile() {
  return <img src="..." />;
}

한 파일에서 여러개 export 하기

// Gallery.js
export function Profile() {
  return <img src="..." />;
}
export default function Gallery() {
  return (
    <div>
      <h1>Gallery</h1>
      <Profile />
    </div>
  );
}
 
 
// App.js
import Gallery from "./Gallery";
import { Profile } from "./Gallery";
export default function App() {
  return (
    <>
      <Gallery />
      <Profile />
    </>
  );
}

3. JSX로 마크업 작성하기

React에서는 마크업을 작성할 때 JSX라는 특별한 문법을 사용한다.

JSX는 HTML과 매우 비슷하게 생겼지만, 사실 JavaScript에 마크업을 넣은 것이다.

 JSX란?

JSX는 JavaScript 확장 문법으로 HTML처럼 보이지만 실제로는 JavaScript 코드이고,

컴포넌트의 마크업을 간결하고 직관적으로 표현할 수 있게 해준다.

export default function TodoList() {
  return (
    <>
      <h1>My Todo</h1>
      <ul>
        <li>Study React</li>
      </ul>
    </>
  );
}

JSX의 규칙

1. 하나의 루트 엘리먼트로 반환하기

// ❌ 오류 발생
return (
  <h1>Hi</h1>
  <p>Hello</p>
);
// ✅ 이렇게 감싸야 함
return (
  <div>
    <h1>Hi</h1>
    <p>Hello</p>
  </div>
);
// ✅ 또는 Fragment도 가능
return (
  <>
    <h1>Hi</h1>
    <p>Hello</p>
  </>
);

2. 모든 태그는 반드시 닫아주기

// ❌ 오류 발생
<img src="..." alt="..." >
<li>Item
// ✅ 이렇게!
<img src="..." alt="..." />
<li>Item</li>

3. 대부분 camelCase 사용

  • 단, aria-*, data-* 속성은 그대로 사용 가능
<img className="jaeeun" />

예시 

  • HTML -> JSX 변환 
// HTML
<h1>Hedy Lamarr's Todos</h1>
<img src="https://i.imgur.com/yXOvdOSs.jpg" alt="Hedy Lamarr" class="photo">
<ul>
  <li>Invent new traffic lights
  <li>Rehearse a movie scene
  <li>Improve the spectrum technology
</ul>
 
 
// JSX
export default function TodoList() {
  return (
    <>
      <h1>Hedy Lamarr's Todos</h1>
      <img
        alt="Hedy Lamarr"
        className="photo"
      />
      <ul>
        <li>Invent new traffic lights</li>
        <li>Rehearse a movie scene</li>
        <li>Improve the spectrum technology</li>
      </ul>
    </>
  );
}

4. 중괄호가 있는 JSX안에서 자바스크립트 사용하기

React에서는 { 중괄호 }를 사용하면 JSX 안에서 JavaScript를 직접 쓸 수 있다.

 

JSX는 기본적으로 HTML처럼 보이지만 실제론 JavaScript 문법 안에서 동작하는 문법!

 

  • 정적인 문자열은 ""로 쓸 수 있고,
  • 동적인 값(변수, 함수, 객체 등)은 { 중괄호 }로 사용한다
//➡️문자열은 따옴표로 전달
export default function Avatar() {
  return (
    <img
      className="avatar"
      alt="Gregorio Y. Zara"
    />
  );
}
 
//➡️중괄호로 변수 넣기
export default function Avatar() {
  const avatar = 'https://i.imgur.com/7vQD0fPs.jpg';
  const description = 'Gregorio Y. Zara';
  return (
    <img
      className="avatar"
      src={avatar}
      alt={description}
    />
  );
}
 
 
//➡️중괄호 안에서 함수 호출도 가능
const today = new Date();
function formatDate(date) {
  return new Intl.DateTimeFormat('en-US', { weekday: 'long' }).format(date);
}
export default function TodoList() {
  return (
    <h1>To Do List for {formatDate(today)}</h1>
  );
}
 
 
//➡️객체를 전달하려면 {{이중 중괄호 }}
<ul style={{
  backgroundColor: 'black',
  color: 'pink'
}}>
  <li>Do something</li>
</ul>

5. 컴포넌트에 Props 전달하기

React에서 props는 컴포넌트끼리 부모 → 자식 방향으로 정보를 주고받는 주요한 방식이다.

객체, 배열, 함수를 포함한 모든 JavaScript 값을 전달할 수 있다.

//➡️props를 컴포넌트에 전달하는 방법
<Avatar
  person={{ name: 'Jaeeun Shin', imageId: '1234' }}
  size={100}
/>
 
 
 
//➡️props의 기본값 설정하기
function Avatar({ person, size = 100 }) {
  // size가 전달되지 않으면 기본값은 100
}
 
 
 
//➡️props를 객체로 한 번에 전달하기 (Spread 문법)
function Profile(props) {
  return <Avatar {...props} />;
}
 
 
//➡️JSX로 컴포넌트를 중첩 → children으로 전달
function Card({ children }) {
  return <div className="card">{children}</div>;
}
<Card>
  <Avatar />
</Card>

props는 시간이 지나면서 바뀔 수 있다

function Clock({ color, time }) {
  return <h1 style={{ color }}>{time}</h1>;
}
 
//props.color = 'red'; 는 안
  • color, time 같은 props는 외부에서 변경될 수 있음
  • React는 새로운 props가 오면 컴포넌트를 재렌더링
  • 하지만 컴포넌트 내부에서는 props를 직접 수정할 수 없음!

6. 조건부 렌더링

React는 if 문, && 및 ? : 연산자와 같은 자바스크립트 문법을 사용하여 조건부로 JSX를 렌더링할 수 있다.

  • if문은 가장 읽기 쉽고 유연함
  • 삼항 연산자는 JSX 안에서 간결하게 조건을 표현할 수 있음
  • &&연산자는 특정 조건일 때만 추가적으로 렌더링할 때 유용
  • null을 반환하면 해당 컴포넌트는 렌더링되지 않음
  • 조건부 JSX는 반복되는 구조를 줄이는 데도 유리
//➡️if / else 문으로 조건 분기
//✔ 직관적이고 명확한 구조 ✖ 중복되는 부분이 많아질 수 있음
function Item({ name, isPacked }) {
  if (isPacked) {
    return <li>{name} ✅</li>;
  }
  return <li>{name}</li>;
}
 
//➡️JSX에서 null 반환으로 무조건 제외하기
//✖ 종종 개발자가 의도한 렌더링을 하지 않아 디버깅이 어려움
if (isPacked) {
  return null; // 아무것도 렌더링하지 않음
}
 
 
 
//➡️삼항 연산자 (? :) 사용
//✔ 간결하고 JSX 내부에서 쓰기 좋음 ✖ 조건이 복잡해지면 가독성 떨어짐
return (
  <li>
    {isPacked ? <del>{name} ✅</del> : name}
  </li>
);
 
 
//➡️논리 AND 연산자 (&&)
//✔ 간단한 “조건 만족 시만 렌더링”에 적합 ✖ 단, 0 && <Component />일 경우 0이 그대로 렌더링됨 주의!
return (
  <li>
    {name} {isPacked && "✅"}
  </li>
);
 
//➡️변수에 조건부 JSX 저장
//✔ 복잡한 조건 처리나 중복 제거에 탁월 ✔ 함수처럼 JSX 분리 가능
function Item({ name, isPacked }) {
  let itemContent = name;
  if (isPacked) {
    itemContent = (
      <del>
        {name} ✅
      </del>
    );
  }
  return <li>{itemContent}</li>;
}

7. 리스트 렌더링

React에서는 여러 데이터를 반복해 UI에 표시할 때 JavaScript 배열 메서드(map, filter 등)를 활용해서 컴포넌트를 배열로 렌더링한다.

//항상 .map() 안에서는 key 필요!
people.map(p => <li key={p.id}>{p.name}</li>);
 
//JSX 안에서 map() 사용 시 암시적 return에 유의
person => <li>{person.name}</li> ✅
person => { <li>{person.name}</li> } ❌ → return 
 
 
//리스트 나누기
const chemists = people.filter(p => p.profession === 'chemist');
const others = people.filter(p => p.profession !== 'chemist');
return (
  <>
    <h2>Chemists</h2>
    <ul>
      {chemists.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
    <h2>Everyone Else</h2>
    <ul>
      {others.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  </>
);
 
 
//컴포넌트 분리 -> 더 깔끔
function PersonList({ people }) {
  return (
    <ul>
      {people.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

8. 컴포넌트를 순수하게 유지하기

순수 컴포넌트란?

  • 같은 props가 들어오면 항상 같은 JSX를 반환하는 컴포넌트
  • 외부의 상태나 변수, 객체를 절대 변경하지 않음
  • 수학 공식처럼 y = 2x → x = 3이면 항상 y = 6이어야 함!

예시) 순수 컴포넌트

  • props로 받은 drinkers 값 만을 기반으로 렌더링
  • 외부의 변수 변경 없이 JSX만 반환 = 100% 순수 
function Recipe({ drinkers }) {
  return (
    <ol>
      <li>Boil {drinkers} cups of water.</li>
      <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li>
      <li>Add {0.5 * drinkers} cups of milk and sugar to taste.</li>
    </ol>
  );
}
예시) 순수하지 않은 컴포넌트
  • 외부 상태인 guest를 변경함
  • 여러 번 렌더링하면 JSX가 계속 달라짐 = 예측 불가능
let guest = 0;
function Cup() {
  guest++; // ❌ 외부 상태를 변경
  return <h2>Tea cup for guest #{guest}</h2>;
}
 
//✅ 순수하게 고치기 => 필요한 값을 props로 받아서 렌더링만 담당
function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}
//❌ 렌더링 중 DOM에 접근하면 에러나거나 React 흐름이 깨짐
document.getElementById('time').className = 'night';
 
//✅ 고친 예시 – 조건에 따라 클래스 적용
export default function Clock({ time }) {
  const hours = time.getHours();
  const className = hours >= 0 && hours <= 6 ? 'night' : 'day';
  return (
    <h1 className={className}>
      {time.toLocaleTimeString()}
    </h1>
  );
}
 
 
//✅ 지역 변경은 OK
export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />);
  }
  return cups; // cups는 렌더링 중에 만든 지역 변수
}
외부 변수 변경 X 렌더링 중 외부 객체/변수 변경 금지
동일 입력 = 동일 출력 같은 props → 항상 같은 결과
지역 변경(O) 컴포넌트 내부에서 만든 변수 수정은 OK
사이드 이펙트 금지 DOM 조작, fetch, 이벤트 등은 렌더링 중 사용 X
변화는 핸들러나 useEffect에서 변화는 렌더링 외부(핸들러, useEffect 등)에서 처리

9. 트리로서의 UI

렌더 트리 (Render Tree)

  • 트리는 요소 간의 계층 관계를 시각적으로 표현하는 방식
  • 웹에서는 HTML, CSS, 컴포넌트 등 대부분의 UI 구조가 트리 형태
  • React도 내부적으로 트리 구조를 통해 컴포넌트를 관리하고 렌더링함

 

<App>
  <FancyText />
  <InspirationGenerator>
    <Copyright />
  </InspirationGenerator>
</App>
-----------------------------
App
├── FancyText
└── InspirationGenerator
     └── Copyright

모듈 의존성 트리 (Dependency Tree)

  • JS 파일(모듈) 간의 import 관계
  • 각 노드는 파일(모듈), 화살표는 import 관계
  • 코드 단위 기준의 트리 구조
 
// App.js
import InspirationGenerator from './InspirationGenerator';
import FancyText from './FancyText';
import Copyright from './Copyright';
 
======================================================
App.js
├── InspirationGenerator.js
│   ├── FancyText.js
│   ├── Color.js
│   └── inspirations.js
├── FancyText.js
└── Copyright.js

 

 렌더 트리 vs 모듈 트리

기준 컴포넌트 렌더링 구조 파일 import 구조
노드 렌더된 컴포넌트 JavaScript 모듈
목적 UI 구조, 렌더링 흐름 이해 번들 크기 및 코드 의존성 파악
조건부 렌더링 영향 있음 없음 (import는 고정)