on my way

한 입 크기로 잘라먹는 리액트 Project1: [카운터] 앱 만들기 본문

Computer Science/React

한 입 크기로 잘라먹는 리액트 Project1: [카운터] 앱 만들기

wingbeat 2024. 7. 16. 10:08
반응형

Project 1: [카운터 앱 만들기]

목차

  1. 프로젝트 준비하기
    • 요구사항 분석하기
    • 리액트 앱 만들기
  2. UI 구현하기
    • Viewer 컴포넌트 만들기
    • Controller 컴포넌트 만들기
    • 컴포넌트 스타일링하기
  3. 기능 구현하기
    • State를 이용해 카운터 기능 구현하기
    • State는 어떤 컴포넌트에 만들까?
    • 리액트답게 설계하기

1. 프로젝트 준비하기

요구사항 분석하기

지금까지 배운 내용을 토대로 [카운터] 앱 프로젝트를 진행했다. [카운터] 앱은 숫자를 더하고 빼는 기능만 있는 아주 단순한 앱이다.

첫 리액트 프로젝트인 만큼 간단한 기능만 포함하고 있어서 누구나 어렵지 않게 구현할 수 있다.

프로젝트를 구현하기에 앞서 꼭 해야 할 일이 있다.

이 앱을 어떤 설계와 기능으로 구현할지 살펴보는 것이다. 이를 소프트웨어 공학에서는 요구사항 분석이라고 한다.

 

리액트 앱 만들기

준비 과정의 마지막 단계는 리액트 앱을 만드는 작업이다. 리액트 앱을 생성하는 방법은 chapter5와 동일하다.

문서(Documents) 폴더 아래에 project1 폴더를 만들고, 비주얼 스튜디오 코드에서 열었다. 계속해서 터미널을 열고 다음 명령어를 입력해 리액트 앱을 생성했다.

 npx create-react-app .

리액트 앱을 정상적으로 만들었다면, 사용하지 않는 파일과 코드도 삭제했다. src 폴더에서 다음 파일들을 삭제했다:

  • src/App.test.js
  • src/logo.svg
  • src/reportWebVitals.js
  • src/setupTest.js

사용하지 않는 코드 역시 삭제했다. index.js를 다음과 같이 수정했다.

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

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

App.js를 다음과 같이 수정했다.

모두 완료한 후 터미널에서 npm run start를 입력해 리액트 앱을 시작했다.

 

const root = ReactDOM.createRoot(document.getElementById("root")) 

리액트 애플리케이션을 브라우저에 렌더링하기 위한 초기 설정을 의미한다. 하나씩 살펴보자.

  • document.getElementById("root"): 이 부분은 HTML 파일에서 id가 "root"인 요소를 가져오는 것이다. 이 요소는 리액트 애플리케이션이 렌더링될 DOM 노드이다. 보통 index.html 파일에 <div id="root"></div> 같은 코드가 있다.
  • ReactDOM.createRoot(...): 이 부분은 리액트 18에서 도입된 새로운 방식으로, React 애플리케이션의 루트(root)를 생성한다. 이 루트는 애플리케이션의 진입점으로 사용된다.
  • const root = ...: 이 코드는 생성된 루트를 root라는 변수에 저장하여 나중에 사용할 수 있게 한다.

 


2. UI 구현하기

Viewer 컴포넌트 만들기

현재의 카운트를 표시하는 Viewer 컴포넌트를 만들었다.

src에 component 폴더를 만들고, 이 폴더에서 Viewer.js를 생성했다.

const Viewer = () => {
  return (
    <div>
      <div>현재 카운트: </div>
      <h1>0</h1>
    </div>
  );
};

export default Viewer;

Viewer 컴포넌트를 페이지에 렌더링하기 위해 App의 자식으로 배치했다.

 

import "./App.css";
import Viewer from "./component/Viewer";

function App() {
  return (
    <div className="App">
      <h1>Simple Counter</h1>
      <section>
        <Viewer />
      </section>
    </div>
  );
}

export default App;

저장하고 페이지에서 결과를 확인했다.

 

Controller 컴포넌트 만들기

다음으로 카운트를 늘리거나 줄이는 Controller 컴포넌트를 만들었다.

component 폴더에서 Controller.js를 만들고 다음과 같이 작성했다.

const Controller = () => {
  return (
    <div>
      <button>-1</button>
      <button>-10</button>
      <button>-100</button>
      <button>+100</button>
      <button>+10</button>
      <button>+1</button>
    </div>
  );
};

export default Controller;

Controller 컴포넌트를 페이지에 렌더링하려면 Viewer처럼 App의 자식으로 배치했다.

 

import "./App.css";
import Controller from "./component/Controller";
import Viewer from "./component/Viewer";

function App() {
  return (
    <div className="App">
      <h1>Simple Counter</h1>
      <section>
        <Viewer />
      </section>
      <section>
        <Controller />
      </section>
    </div>
  );
}

export default App;

저장하고 페이지에서 결과를 확인했다. 이렇게 [카운터] 앱의 페이지 UI 구현을 완료했다.

 

컴포넌트 스타일링하기

요구사항에 맞게 적절한 스타일 규칙을 적용했다.

src 폴더 App.css에서 기존 스타일 규칙을 모두 삭제하고 다음과 같이 작성했다.

body {
  padding: 20px;
}

.App {
  margin: 0 auto;
  width: 500px;
}

.App > section {
  padding: 20px;
  background-color: rgb(245, 245, 245);
  border: 1px solid rgb(240, 240, 240);
  border-radius: 5px;
  margin-bottom: 10px;
}

컴포넌트 스타일링까지 완료한 후 저장하고 지금까지 작업한 결과를 확인했다.


3. 기능 구현하기

State를 이용해 카운터 기능 구현하기

카운터의 기능을 한 문장으로 정의하면 다음과 같다.

"Controller 컴포넌트에 있는 버튼을 클릭하면, Viewer 컴포넌트에 있는 카운트가 증가하거나 감소해야 한다."

버튼을 클릭하면 카운트 값이 변한다. 이 변화를 처리하기 위해 State를 사용해야 한다.

 

State는 어떤 컴포넌트에 만들까?

State는 반드시 컴포넌트 함수 안에 만들어야 한다.

현재 프로젝트에는 App, Viewer, Controller 3개의 컴포넌트가 있다.

그렇다면 어떤 컴포넌트에

서 State를 만들어야 할까?

Viewer, Controller 컴포넌트에서 State를 만들 수 없는 이유

정답은 App 컴포넌트다. 이유는 다음과 같다.

Viewer 또는 Controller 컴포넌트에 State를 만들고 이 State를 이용해 카운트 기능을 구현하면 Viewer와 Controller 컴포넌트가 부모-자식 관계가 아니므로 값 전달이 불가능하다.

App 컴포넌트에 State를 만들고 카운트 기능을 완성했다.

 

리액트에서 State는 컴포넌트의 상태를 관리하는 데 사용된다.

이 상태는 변경될 수 있으며, 변경될 때마다 컴포넌트는 다시 렌더링된다.

  • App 컴포넌트에 State를 만든다는 것은 App 컴포넌트 안에 상태를 관리하는 변수를 생성한다는 의미이다.
  • useState 훅을 사용하여 상태를 생성하고 관리한다.
import "./App.css";
import { useState } from "react";
import Controller from "./component/Controller";
import Viewer from "./component/Viewer";

function App() {
  const [count, setCount] = useState(0);
  const handleSetCount = (value) => {
    setCount(count + value);
  };

  return (
    <div className="App">
      <h1>Simple Counter</h1>
      <section>
        <Viewer count={count} />
      </section>
      <section>
        <Controller handleSetCount={handleSetCount} />
      </section>
    </div>
  );
}

export default App;

Viewer 컴포넌트에서 App에서 받은 Props를 페이지에 렌더링했다.

const Viewer = ({ count }) => {
  return (
    <div>
      <div>현재 카운트 : </div>
      <h1>{count}</h1>
    </div>
  );
};

export default Viewer;

Controller 컴포넌트에서 App 컴포넌트의 handleSetCount를 받아 버튼의 이벤트 핸들러로 사용했다.

 

const Controller = ({ handleSetCount }) => {
  return (
    <div>
      <button onClick={() => handleSetCount(-1)}>-1</button>
      <button onClick={() => handleSetCount(-10)}>-10</button>
      <button onClick={() => handleSetCount(-100)}>-100</button>
      <button onClick={() => handleSetCount(100)}>+100</button>
      <button onClick={() => handleSetCount(10)}>+10</button>
      <button onClick={() => handleSetCount(1)}>+1</button>
    </div>
  );
};

export default Controller;

저장하고 카운트 기능이 잘 구현되는지 확인했다. 최종적으로, [카운터] 앱의 기능 구현을 완료했다.


리액트답게 설계하기

리액트는 규모가 크고 빠른 웹 애플리케이션을 만들기 좋은 기술이다.

이를 위해 리액트가 권장하는 애플리케이션 설계 방식에 대해 살펴보았다. 리액트에서 컴포넌트 간에 데이터를 전달할 때는 Props를 사용하는데, 전달 방향은 언제나 부모로부터 자식에게 전달하는 방식이다.

이를 단방향 데이터 흐름이라고 한다.

  

리액트에서 데이터 흐름은 단방향이다. 이는 데이터의 흐름을 이해하기 쉽고, 관리하기 좋다는 장점이 있다.

이것은 부모 컴포넌트가 자식 컴포넌트에 데이터를 전달할 수 있지만, 자식 컴포넌트는 부모 컴포넌트에 직접적으로 데이터를 전달할 수 없다는 것을 의미한다. 

대신, 자식 컴포넌트가 부모 컴포넌트의 상태를 변경해야 할 때는 역방향으로 이벤트를 전달해야 한다.

  • 단방향 데이터 흐름: 데이터가 부모에서 자식으로만 전달된다.
  • 역방향 이벤트 전달: 자식 컴포넌트에서 발생한 이벤트는 부모 컴포넌트로 전달되어 부모의 상태를 변경한다.

 

간단한 [카운터] 앱에서는 Controller 컴포넌트의 버튼 클릭 이벤트가 App 컴포넌트의 State를 업데이트한다.

 

App 컴포넌트는 이 업데이트 함수를 Props로 전달하여 자식 컴포넌트가 부모의 State를 변경할 수 있도록 한다.

결론적으로, 리액트 앱을 설계할 때는 데이터는 위에서 아래로, 이벤트는 아래에서 위로 향하도록 설계해야 한다.

 
반응형