DEVELOPMENT/react

7. 카운터 앱만들기

Tiny Commit 2024. 11. 11. 10:33

 

[카운터] 앱은 숫자를 더하고 빼는 기능만 있는 아주 단순한 앱입니다. 

어떤 설계와 기능으로 구현할지 생각 -> 프로젝트 구현

 

1. 요구사항 분석하기

  • 뷰어(Viewer): 현재의 카운트 표시
  • 컨트롤러(Controller): 카운트를 제어하는 영역

(1) 컨포넌트 단위로 생각하기

  • App 컴포넌트: Viewer와 Controller 컴포넌트를 감싸는 템플릿 
  • Viewer 컴포넌트: 현재의 카운트를 표시함 
  • Controller 컴포넌트: 카운트를 제어할 수 있는 기능을 제공함 

하나의 컨ㅍ포넌트가 여러 기능을 갖게 할 수 있지만, 코드 관리가 어려워진다.

-> 컨포넌트를 재사용이 가능한 수준으로 잘게 쪼개어 개발 필요

 

"하나의 컨포넌트는 단 하나의 역할만 수행한다."

 

 

(2) 리액트 앱 만들기

폴서 생성 -> 터미널 명령어 입력: " npx create-react-app . " -> 불필요한 파일 삭제 (src/App.test.js, src/logo.svg, src/reportWebVitals.js, src/setupTest.js)

 

// src/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 />);
// src/App.js
import "./App.css";

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

export default App;

 

 

 

 

2. UI 구현하기

UI는 사용자 인터페이스라는 뜻으로, 웹 페이지에서 사용자와 상호작용하는 요소를 말합니다.

이 요소들의 사용성을 높이기 위해 기능을 추가하기도 하고, 특별한 형태나 색상 들의 스타일을 적용하기도 합니다.

 

 

(1) Viewer 컨포넌트 만들기

폴서 생성: src -> component -> Viewer.js

// src/component/Viewer.js
const Viewer = () => {
  return (
    <div>
      <div>현재 카운트: </div>
      <h1>0</h1>
    </div>
  );
};
export default Viewer;

 

Viewer: 두 줄에 걸처 텍스트 출력 

 

 

 

// src/App.js
import "./App.css";
import Viewer from "./component/Viewer"; // Viewer 컴포넌트를 불러오기

function App() {
  return (
    <div className="App">
      <h1>Simple Counter</h1> // 제목을 <h1> 태그로 감싸 페이지에서 렌더링
      <section>
        <Viewer /> //  Viewer 컴포넌트를 불러와 <section> 태그로 감싸 렌더링
      </section> // <section>은 영역을 분리하기 위한 태그로 <div>와 동일한 기능을 수행
    </div> 
  );
}

export default App;

 

 

 

 

(2) Controller 컴포넌트 만들기

// src/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: 6개 버튼을 한 줄로 렌더링\

 

 

// src.App.js
import "./App.css";
import Controller from "./component/Controller"; // Controller 컴포넌트를 불러오기
import Viewer from "./component/Viewer";

function App() {
  return (
    <div className="App">
      <h1>Simple Counter</h1>
      <section>
        <Viewer />
      </section>
      <section>
        <Controller /> // Controller 컴포넌트를 <section> 태그로 감싸 렌더링
      </section>
    </div>
  );
}

export default App;

 

 

 

(3) 컴포넌트 스타일링하기

src 폴더 App.css에서 기존 스타일 규칙을 모두 삭제

// src/App.css
body {
  padding: 20px;
}

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

.App > section { // className=App 요소의 <section> 태그를 가리키는 CSS 문법, 스타일 규칙이 <section>에만 적용
  padding: 20px;
  background-color: rgb(245, 245, 245);
  border: 1px solid rgb(240, 240, 240);
  border-radius: 5px;
  margin-bottom: 10px;
}

 

 

 

 

 

 

 

 

 

3. 기능 구현하기

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

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

( Controller 컴포넌트에 있는 <+100> 버튼을 클릭하면 Viewer 컴포넌 트의 숫자는 0에서 100으로 바뀌어야 합니다. ) 

 

 

 

State를 이용해 버튼 클릭 이벤트가 발생했을 때 컴포넌트 값을 동적으로 랜더링 하는 과

State생성 후, 초기값을 0으로 설정 -> Controller 컨포넌트의 버튼을 클릭하면 현재 State값을 버튼이 전달하는 값과 계산해 변경 -> 변경된 State값을 Voewer컴포넌트에 전달하여 페이지의 카운트 값 업데이

 

 

 

 

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

 App, Viewer, Controller 3개의 컴포넌트중 App컴포넌트에 State를 만들어야 한다.

-> State 값은 Viewer 컴포넌트, set 함수는 Controller 컴포넌트에 전달해야 하기 때문입니다. 

 

리액트는 State 값이나 set함수를 여러 컴포넌트에서 사용하는 경우, 이들을 상위 컴포넌트에서 관리합니다. 리액트에서는 이 기능을 다른 말로 ‘State 끌어올리기(State Lifting)’라고 합니다.

 

 

 

a. 오답1: Viewer 컴포넌트

Viewer 컴포넌트가 Controller 컴포넌트에 setCount를 전달할 방법이 없다는 겁니다. 5장에서 살펴보았듯이 리액트에서 컴포넌트가 다른 컴포넌트에 데이터를 전달할 때는 Props를 사용하는데, Props는 부모만이 자식에게 전달할 수 있습니다. Viewer와 Controller 컴포넌트는 부모-자식 관계가 아니므로 어떠한 값도 전달할 수 없습니다.

 

 

 

b. 오답2: Contreller 컴포넌트

버튼을 클릭하면 State는 기존 값에서 해당 버튼의 숫자와 계산한 값으로 변경됩니다. 그러나 여기서도 문제가 있습니다. 변경된 State 값을 Viewer 컴포넌트에 전달할 방법이 없기 때문입니다. 
다시 말해 State 변수 count를 Viewer 컴포넌트에 전달해야 하는데, Viewer와 Controller컴포넌트는 부모-자식 관계가 아니므로 그렇게 할 수 없습니다.

 

c. 정딥: App 컴포넌트

// src/App.js

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} /> // Viewer 컴포넌트에 State 변수 count의 값을 Props로 전달
      </section>
      <section>
        <Controller handleSetCount={handleSetCount} /> //  Controller 컴포넌트에 State 값을 변경하는 함수 setCount를 Props로 전달
      </section>
    </div>
  );
}
export default App;

 

App 컴포넌트에서 받은 Props를 페이지에 렌더링합니다. 
리액트에서는 부모가 리렌더되거나 전달된 Props가 변경되면 자식 컴포넌트도 자동으로 리렌더됩니다. 
따라서 Viewer 컴포넌트는 Props로 받은 State 값이 변경될 때 마다 리렌더되어 실시간으로 이 값을 페이지에 렌더링합니다.

 

 

 

// src/component/Controller.js
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;

 

App 컴포넌트에서 함수 handleSetCount를 받아 버튼의 이벤트 핸들러로 사용합니다. 버튼을 클릭하면 함수 handleSetCount를 호출하는데, 이 함수는 App 컴포넌트의 State 값을 업데이트합니다.

 

(3) 리액트답게 설계하기

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

리액트 권장 애플리케이션 설계 방식

 

 

 

리액트에서 컴포넌트 간에 데이터를 전달할 때는 Props를 사용하는데, 전달 방향 은 언제나 부모로부터 자식에게 전달하는 방식입니다. 리액트의 이러한 데이터 전달 특징을 ‘단방향 데이터 흐름’이라고 합니다. 

리액트의 단방향 데이터 전달은 데이터의 흐름을 이해하기 쉽고, 관리하기 좋다는 장점이 있습니다

 

.
반면 State를 변경하는 이벤트는 자식에서 부모를 향해 역방향으로 전달되어야 합니다.

Controller 컴포넌트에 있는 버튼 요소를 클릭할 때마다 App 컴포넌트의 State를 업데이트하는 이벤트가 발생합니다. App 컴포넌트는 자신이 관리하는 State를 변경하는 함수를 Props로 전달해 자식이 부모의 State를 대신 업데이트하게 했습니다.  

 

 

 

 

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

 

 

 

 

 


 


출처 :  이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).