icon
11장 : 프론트엔드

React 기초 (컴포넌트, JSX, 상태 관리)

지난 장에서 우리는 Single Page Application (SPA)이 무엇이며, 왜 현대 웹 개발의 주류가 되었는지 살펴보았습니다. SPA는 사용자에게 데스크톱 애플리케이션과 같은 매끄럽고 빠른 경험을 제공하며, 이를 구현하기 위해서는 클라이언트 측에서 동적으로 UI를 업데이트하는 복잡한 JavaScript 로직이 필요합니다.

이러한 복잡한 UI 개발을 효율적으로 돕기 위해 등장한 것이 바로 프론트엔드 프레임워크/라이브러리입니다. 그중에서도 Facebook(현재 Meta)에서 개발한 React는 전 세계적으로 가장 많은 개발자가 사용하고 있는 UI 라이브러리 중 하나입니다. React는 "사용자 인터페이스를 구축하기 위한 JavaScript 라이브러리"라고 스스로를 정의하며, "선언형(Declarative)" 방식으로 UI를 쉽게 만들고 관리할 수 있도록 돕습니다.

이번 장에서는 React의 핵심 개념인 컴포넌트(Component), JSX, 상태(State), 속성(Props) 을 중심으로 React 애플리케이션을 개발하는 기본적인 방법을 학습합니다.


React의 등장 배경과 특징

React는 2013년에 Facebook에 의해 오픈소스로 공개되었습니다. 당시 웹 개발은 jQuery와 같은 라이브러리를 통해 DOM을 직접 조작하는 방식이 주를 이루고 있었는데, 애플리케이션의 규모가 커지고 UI가 복잡해지면서 DOM 조작의 복잡성과 성능 문제가 대두되기 시작했습니다.

React는 이러한 문제에 대한 해결책으로 등장했으며, 다음과 같은 주요 특징을 가집니다.

  • 선언형(Declarative): DOM을 직접 조작하는 명령형(Imperative) 방식과 달리, React는 개발자가 "이렇게 보이도록 해달라"고 선언하면, React가 알아서 그 상태를 만들기 위해 DOM을 효율적으로 업데이트합니다.
  • 컴포넌트 기반(Component-Based): UI를 독립적이고 재사용 가능한 작은 조각들인 컴포넌트로 분리하여 개발합니다. 이는 코드의 재사용성을 높이고 유지보수를 용이하게 합니다.
  • 가상 DOM (Virtual DOM): 실제 DOM에 직접 접근하여 조작하는 것은 느리고 비효율적입니다. React는 실제 DOM의 사본인 가상 DOM을 메모리에 유지하고, 변경 사항이 발생하면 가상 DOM을 먼저 업데이트한 후, 이전 가상 DOM과 비교하여 변경된 부분만 실제 DOM에 효율적으로 반영(재조정, Reconciliation)합니다. 이는 성능 향상에 크게 기여합니다.
  • 단방향 데이터 흐름 (Unidirectional Data Flow): 데이터가 부모 컴포넌트에서 자식 컴포넌트로 단방향으로 흐릅니다(Props를 통해). 이는 데이터 흐름을 예측 가능하게 하고 디버깅을 용이하게 합니다.

React 개발 환경 설정 (CRA 사용)

가장 간단하게 React 개발 환경을 설정하는 방법은 Create React App (CRA)을 사용하는 것입니다. CRA는 React 애플리케이션을 시작하는 데 필요한 모든 빌드 도구(Webpack, Babel 등) 설정을 미리 구성해 줍니다.

1. Node.js 및 npm/Yarn 설치 확인

React는 Node.js 환경에서 동작하므로, 미리 설치되어 있어야 합니다.

node -v
npm -v
# 또는
yarn -v

2. 새로운 React 프로젝트 생성

npx create-react-app my-react-app
# 또는
yarn create react-app my-react-app

my-react-app은 생성될 프로젝트의 이름입니다.

3. 프로젝트 디렉토리로 이동

cd my-react-app

4. 개발 서버 실행

npm start
# 또는
yarn start

이 명령어를 실행하면 개발 서버가 시작되고, 브라우저가 자동으로 http://localhost:3000 (기본값)을 열어 React 애플리케이션을 보여줍니다.


JSX: JavaScript + XML/HTML

React에서 UI를 정의할 때 사용되는 문법 확장인 JSX(JavaScript XML) 입니다. 이는 JavaScript 코드 내부에 HTML과 유사한 마크업을 작성할 수 있도록 해줍니다.

특징

  • JavaScript 코드 내에서 UI 구조를 선언적으로 표현: 가독성이 높고 UI와 로직을 한 파일에서 관리하기 용이합니다.
  • JavaScript 표현식 포함: 중괄호 {}를 사용하여 JSX 내부에서 JavaScript 표현식을 포함할 수 있습니다.
  • 클래스 대신 className: HTML의 class 속성은 JavaScript의 예약어이므로, JSX에서는 className을 사용합니다.
  • 자체 닫는 태그: HTML과 달리, 내용이 없는 태그는 반드시 />로 닫아야 합니다 (예: <img />, <input />).
  • 최상위 요소: 하나의 컴포넌트가 여러 요소를 렌더링하려면, 반드시 하나의 최상위 요소로 감싸야 합니다 (예: <div>...</div>, <Fragment>...</Fragment> 또는 ).

예시 (src/App.js)

import React from 'react'; // React 라이브러리 임포트
import './App.css'; // CSS 파일 임포트

function App() {
  const name = "리액트";
  const isLoggedIn = true;

  return (
    // JSX는 반드시 하나의 최상위 요소로 감싸져야 함
    <div className="App">
      <header className="App-header">
        <h1>Hello, {name}!</h1> {/* JSX 내부에서 JavaScript 표현식 사용 */}
        {isLoggedIn ? ( // 조건부 렌더링
          <p>환영합니다!</p>
        ) : (
          <p>로그인 해주세요.</p>
        )}
        <button onClick={() => alert('버튼 클릭됨!')}>클릭하세요</button> {/* 이벤트 핸들러 */}
      </header>
    </div>
  );
}

export default App; // 컴포넌트 내보내기

JSX는 브라우저가 직접 이해할 수 있는 문법이 아닙니다. Babel과 같은 트랜스파일러를 통해 일반 JavaScript로 변환된 후 브라우저에서 실행됩니다. (CRA가 이 과정을 자동으로 처리해 줍니다.)


컴포넌트 (Component)

React 애플리케이션은 UI를 독립적이고 재사용 가능한 작은 블록인 컴포넌트(Component) 의 조합으로 구성됩니다. 컴포넌트는 마치 레고 블록처럼 조합하여 복잡한 UI를 쉽게 구축할 수 있도록 합니다.

React 컴포넌트는 크게 두 가지 방식으로 작성할 수 있습니다.

  • 함수 컴포넌트 (Function Components): 함수 형태로 작성되는 컴포넌트로, React 16.8(Hooks 도입) 이후 주된 방식으로 사용됩니다.
  • 클래스 컴포넌트 (Class Components): ES6 클래스 문법으로 작성되는 컴포넌트로, React.Component를 상속받습니다. 이전 버전 React에서 주로 사용되었습니다. (본 장에서는 함수 컴포넌트 위주로 설명)

함수 컴포넌트 예시 (src/Greeting.js)

// src/Greeting.js
import React from 'react';

// Props를 인자로 받음
function Greeting(props) {
  return (
    <h2>안녕하세요, {props.name}님!</h2>
  );
}

export default Greeting;

컴포넌트 사용 (src/App.js에서 임포트 후 사용)

// src/App.js
import React from 'react';
import Greeting from './Greeting'; // Greeting 컴포넌트 임포트
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>React 기초</h1>
        <Greeting name="개발자" /> {/* Greeting 컴포넌트 사용, name 속성 전달 */}
        <Greeting name="리액트 사용자" /> {/* 여러 번 재사용 가능 */}
      </header>
    </div>
  );
}

export default App;

위 예시에서 Greeting 컴포넌트는 name이라는 props를 받아 사용자에게 인사말을 표시합니다.


상태 (State)

상태(State) 는 컴포넌트 내부에서 시간이 지남에 따라 변경될 수 있는 데이터를 의미합니다. 사용자의 상호작용(버튼 클릭, 텍스트 입력 등)에 따라 UI가 동적으로 변해야 할 때 state를 사용합니다.

함수 컴포넌트에서는 useState 훅(Hook)을 사용하여 상태를 관리합니다.

// src/Counter.js
import React, { useState } from 'react'; // useState 훅 임포트

function Counter() {
  // useState 훅 사용: [현재 상태 값, 상태를 업데이트하는 함수] = useState(초기값)
  const [count, setCount] = useState(0); // count는 0으로 초기화

  const increment = () => {
    setCount(count + 1); // setCount 함수를 사용하여 상태 업데이트
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <h3>카운터: {count}</h3>
      <button onClick={increment}>증가</button>
      <button onClick={decrement}>감소</button>
    </div>
  );
}

export default Counter;

useState 훅의 특징

  • useState(초기값): 초기 상태 값을 인자로 받습니다.
  • 반환 값: [현재 상태 값, 상태를 업데이트하는 함수] 형태의 배열을 반환합니다.
  • 불변성(Immutability): setCount와 같은 상태 업데이트 함수를 호출할 때, 직접 상태를 변경(count++와 같이)하는 대신 새로운 값을 전달해야 합니다. React는 새로운 값이 이전 값과 다를 때만 컴포넌트를 다시 렌더링합니다.

속성 (Props)

속성(Props) 은 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 사용됩니다. props는 "properties"의 줄임말이며, 읽기 전용(Read-only)입니다. 즉, 자식 컴포넌트는 전달받은 props를 변경할 수 없습니다.

// src/Item.js
import React from 'react';

function Item(props) {
  // props 객체에서 필요한 속성들을 구조 분해 할당으로 가져올 수도 있음
  const { name, price } = props;
  return (
    <li>
      {name} - {price}
    </li>
  );
}

export default Item;
// src/App.js (Item 컴포넌트 사용 예시)
import React from 'react';
import Item from './Item'; // Item 컴포넌트 임포트
import Counter from './Counter'; // Counter 컴포넌트 임포트
import './App.css';

function App() {
  const products = [
    { id: 1, name: '노트북', price: 1200000 },
    { id: 2, name: '마우스', price: 30000 },
    { id: 3, name: '키보드', price: 80000 },
  ];

  return (
    <div className="App">
      <header className="App-header">
        <h1>쇼핑 목록</h1>
        <ul>
          {/* 배열을 map 함수로 순회하여 Item 컴포넌트 렌더링 */}
          {products.map(product => (
            // key prop은 리스트 렌더링 시 React가 각 아이템을 식별하는 데 사용
            // 고유한 값을 가져야 함 (여기서는 product.id)
            <Item key={product.id} name={product.name} price={product.price} />
          ))}
        </ul>

        <hr />
        <Counter /> {/* Counter 컴포넌트 사용 */}
      </header>
    </div>
  );
}

export default App;

props는 데이터가 부모에서 자식으로 "단방향"으로 흐르는 React의 핵심 원칙을 보여줍니다.


마무리하며

이번 장에서는 SPA 구현을 위한 가장 인기 있는 라이브러리 중 하나인 React의 기초를 탄탄하게 다졌습니다.

여러분은 React의 등장 배경과 선언형, 컴포넌트 기반, 가상 DOM, 단방향 데이터 흐름과 같은 주요 특징들을 이해했습니다. Create React App을 사용하여 기본적인 개발 환경을 설정하는 방법을 익혔고, JSX 문법으로 JavaScript 코드 내에서 UI를 선언적으로 작성하는 법을 배웠습니다. 또한, React의 핵심 빌딩 블록인 컴포넌트의 개념을 이해하고, 컴포넌트 내부에서 변경되는 데이터를 관리하는 상태(State) 와 부모 컴포넌트로부터 전달되는 속성(Props) 을 사용하여 동적인 UI를 구축하는 방법을 실습했습니다.

이제 여러분은 React를 사용하여 기본적인 웹 컴포넌트를 만들고, 이들을 조합하여 간단한 애플리케이션을 만들 수 있는 능력을 갖추게 되었습니다. React는 매우 방대한 라이브러리이므로, 이 장에서 다룬 기초를 바탕으로 더 깊이 있는 Hooks (useEffect, useContext 등), 생명주기, 라우팅(React Router), 상태 관리 라이브러리(Redux, Zustand) 등의 개념들을 학습해나가야 합니다.