[React] 예제로 배우는 컴포넌트 설계 및 구현
Die 컴포넌트
Die 컴포넌트는 가장 기본이 되는 부분입니다. 상태 없이 값을 렌더링하는 역할을 합니다. 여기서는 <div>로 감싸진 값만을 표시하므로 매우 간단합니다.
// die.jsx
import './Die.css';
const Die = ({ val }) => (
<div className="Die" style={{ backgroundColor: 'slateblue' }}>
{val}
</div>
);
export default Die;
Die 컴포넌트에는 CSS 스타일이 포함되어 있습니다. Die.css 파일을 만들고 스타일을 적용하면 완성됩니다.
Dice 컴포넌트
Dice 컴포넌트는 여러 개의 Die 컴포넌트를 렌더링하는 역할을 합니다. 주어진 dice 배열을 기반으로 Die 컴포넌트를 반복해서 생성합니다.
// Dice.jsx
import Die from "./Die";
import "./Dice.css";
function Dice({ dice }){
return (
<setcion className="Dice">
{dice.map((v, i) =>(
<Die key={i} val={v} />
))}
</setcion>
);
}
export default Dice;
Dice 컴포넌트에도 CSS 스타일이 포함되어 있습니다. 스타일을 추가하면 주사위들이 예쁘게 나타날 것입니다.
게임 로직 구현
이제 Lucky7 게임의 최종 단계를 만들 차례입니다. 주사위를 여러 번 굴리는 등의 게임 로직을 추가하면 됩니다. 이 부분은 프로젝트의 세부사항에 따라 다르게 구현될 수 있습니다. 주사위를 굴릴 때마다 Dice 컴포넌트를 업데이트하고, 특정 조건을 충족하면 승리하는 등의 로직을 추가합니다.
LuckyN 컴포넌트: 주사위 게임의 핵심
이제 LuckyN 컴포넌트로 넘어가 봅시다. LuckyN은 실제 게임 로직을 담당하며 상태를 관리하는 컴포넌트입니다. 이 컴포넌트에 주사위 결과를 저장하지 않으면, 각 Die를 굴려 나온 수를 비교할 방법이 없습니다. 이를 통해 게임에서 우승 여부를 판단할 수 있습니다. LuckyN은 Dice 컴포넌트를 렌더링하고, 각 Die에 주사위 숫자를 전달합니다.
import { useState } from "react";
import { getRolls } from "./utils";
import Button from "./Button";
import Dice from "./Dice";
function LuckyN({ title = "Dice Game", numDice = 2, winCheck }) {
// 주사위 배열 상태
const [dice, setDice] = useState(getRolls(numDice));
// 우승 여부 판단
const isWinner = winCheck(dice);
// 주사위 다시 굴리기
const roll = () => setDice(getRolls(numDice));
return (
<main className="LuckyN">
<h1>
{title} {isWinner && "You Win!"}
</h1>
<Dice dice={dice} />
<Button clickFunc={roll} label="Re-Roll" />
</main>
);
}
export default LuckyN;
여기서 LuckyN 컴포넌트는 주사위 개수(numDice)와 목표 값(goal)을 프로퍼티로 받습니다. 상태로는 dice 배열을 가지며, 이 배열은 주사위 숫자를 담습니다. LuckyN은 간단한 게임 로직과 주사위를 다시 굴리는 기능을 가지고 있습니다.
//utils.js
/** Gets random integer: [1..6]. */
function d6() {
return Math.floor(Math.random() * 6) + 1;
}
/** Get n rolls => [num, ...]. */
function getRolls(n) {
return Array.from({ length: n }, () => d6());
}
/** Get sum of nums. */
function sum(nums) {
return nums.reduce((prev, cur) => prev + cur, 0);
}
export { d6, getRolls, sum };
이제 이 컴포넌트를 통해 주사위 게임의 여러 변종을 만들 수 있습니다. 예를 들어, 주사위 개수를 늘리거나 목표 값을 변경하여 다양한 게임을 즐길 수 있습니다. LuckyN 컴포넌트는 재사용 가능하며, 주사위 게임의 기본 기능을 제공합니다.
상태를 프로퍼티로 사용하기: 리액트의 흔한 용법
이전 비디오에서 살펴본 것처럼, 리액트에서 흔히 사용되는 패턴 중 하나는 상태(state)를 프로퍼티(props)로 전달하는 것입니다. 이는 LuckyN 컴포넌트에서 Dice 컴포넌트로 state를 전달하는 과정으로 구현되었습니다.
상태를 프로퍼티로 전달하기
LuckyN 컴포넌트에는 dice라는 state가 존재합니다. 이 state를 Dice 컴포넌트로 전달하면, Dice는 그 값을 가져와 각 Die 컴포넌트에 전달하고, Die 컴포넌트에서는 실제 숫자를 렌더링합니다. 이는 리액트에서 자주 사용되는 패턴 중 하나입니다.
컴포넌트 간의 데이터 흐름
상태는 주로 한 컴포넌트에서만 사용되고 해당 컴포넌트 내에서 관리됩니다. 그러나 상태를 프로퍼티로 전달함으로써, 데이터는 위에서 아래로 흐르게 됩니다. LuckyN에서 Dice로, 그리고 Die로 데이터가 전달되는 것이죠.
주의사항
한 가지 주의할 점은 전달된 state를 해당 컴포넌트에서 변경할 수는 없다는 것입니다. 예를 들어 Die 컴포넌트에서 LuckyN의 state를 직접 변경할 수 없습니다. 데이터의 변경은 항상 상위 컴포넌트에서 이루어져야 합니다.
함수 전달
이 패턴에서 주목할 만한 부분은 함수를 프로퍼티로 전달할 수 있다는 것입니다. 이는 리액트의 강력한 기능 중 하나로, 후속 강의에서 자세히 다룰 예정입니다.
간결하게 말하자면, 현재로서는 데이터를 전달할 수 있으면 충분하며, 상태와 프로퍼티를 활용하여 리액트 애플리케이션을 효율적으로 구축할 수 있습니다.
함수를 프로퍼티로 전달하기: 리액트의 강력한 활용
리액트에서의 프로그래밍에서 로직을 최소화하고 컴포넌트를 간결하게 유지하려면 유틸리티 함수를 활용하는 것이 좋습니다. 유틸리티 함수를 모아둔 파일은 컴포넌트와는 무관하게 로직을 재사용 가능하게 만들어 여러 컴포넌트에서 활용할 수 있습니다.
유틸리티 함수 활용
이미 알려진 개념이지만, 실제 컴포넌트에 로직을 넣지 않고 외부 유틸리티 함수를 활용하면 코드가 깔끔해지고 재사용성이 높아집니다. 리액트 컴포넌트와 관계 없는 함수들을 유틸리티 파일에 모아 정리하는 것은 좋은 관행 중 하나입니다.
함수를 Props로 전달하기
다음으로 중요한 개념은 함수를 props로 전달할 수 있다는 것입니다. 이는 매우 유용한 상황에서 활용됩니다. 예를 들어, 게임 로직에서 목표를 달성하는 함수를 외부에서 전달받아 컴포넌트 내에서 활용할 수 있습니다.
함수 전달의 장점
이렇게 하면 게임 로직을 컴포넌트 내에 고정하지 않고, 외부에서 유연하게 설정할 수 있습니다. 컴포넌트의 재사용성이 높아지며, 다양한 게임 변종을 만들기가 용이해집니다.
함수를 props로 전달하는 것은 리액트의 강력한 기능 중 하나로, 부모 컴포넌트에서 온 state를 자식 컴포넌트에서 업데이트하는 방법과 함께 자세히 다루어질 예정입니다. 현재로서는 함수를 props로 전달하는 개념을 확인했으며, 이는 리액트 애플리케이션을 더 효율적으로 만드는 데 도움이 됩니다.
상태를 업데이트하는 함수 전달
이번에는 리액트에서 자주 사용되는 개념 중 하나인 "함수를 props로 전달"하는 방법을 살펴보겠습니다. 이를 통해 부모 컴포넌트의 state를 자식 컴포넌트에서 업데이트할 수 있게 됩니다. 아래의 코드를 통해 구체적인 예제를 확인하고 정리해보겠습니다.
이번에는 리액트에서 자주 사용되는 개념 중 하나인 "함수를 props로 전달"하는 방법을 살펴보겠습니다. 이를 통해 부모 컴포넌트의 state를 자식 컴포넌트에서 업데이트할 수 있게 됩니다. 아래의 코드를 통해 구체적인 예제를 확인하고 정리해보겠습니다.
버튼을 별도의 컴포넌트로 추상화하고 재사용성을 높이는 방법을 살펴보겠습니다.
이 버튼 컴포넌트를 만들어서 다른 컴포넌트에서도 사용할 수 있게끔 하겠습니다.
// Button.jsx
import "./Button.css";
function Button({ clickFunc, label = "Click Me" }) {
return (
<button onClick={clickFunc} className="Button">
{label}
</button>
);
}
export default Button;
이렇게 만들어진 버튼 컴포넌트는 label과 clickFunc라는 두 가지 props를 받습니다. label은 버튼에 표시될 텍스트이고, clickFunc은 버튼을 클릭했을 때 호출되는 함수입니다. 버튼을 클릭하면 이 함수가 실행되게 됩니다.
다음으로 버튼을 사용하는 LuckyN 컴포넌트에서 이 버튼을 렌더링하고 클릭 이벤트에 함수를 연결해보겠습니다.
// LuckyN.jsx
import { useState } from "react";
import { getRolls } from "./utils";
import Button from "./Button";
import Dice from "./Dice";
function LuckyN({ title = "Dice Game", numDice = 2, winCheck }) {
const [dice, setDice] = useState(getRolls(numDice));
const isWinner = winCheck(dice);
const roll = () => setDice(getRolls(numDice));
return (
<main className="LuckyN">
<h1>
{title} {isWinner && "You Win!"}
</h1>
<Dice dice={dice} />
<Button clickFunc={roll} label="Re-Roll" />
</main>
);
}
export default LuckyN;
여기서 주목해야 할 부분은 Button 컴포넌트를 사용할 때 clickFunc prop으로 roll 함수를 전달한 것입니다. 이제 Button 컴포넌트에서 클릭 이벤트가 발생하면 roll 함수가 호출되어 LuckyN 컴포넌트의 state를 업데이트합니다.
이와 같은 방식으로 함수를 props로 전달하면 부모 컴포넌트에서 자식 컴포넌트의 동작을 제어할 수 있습니다. 이는 리액트에서 상태 관리와 관련된 중요한 개념 중 하나입니다. 함수를 전달함으로써 컴포넌트 간의 효과적인 소통이 이루어집니다.
상태를 업데이트하는 함수 전달하기
리액트에서 상태(state)를 업데이트하는 함수를 부모 컴포넌트에서 자식 컴포넌트로 전달하고 활용하는 방법을 알아보겠습니다.
버튼 컴포넌트 만들기
먼저, 상태를 업데이트할 버튼 컴포넌트를 만들어봅시다.
// Button.jsx
import React from 'react';
import './Button.css';
const Button = ({ clickFunc, label = 'Click Me' }) => {
return (
<button className="Button" onClick={clickFunc}>
{label}
</button>
);
};
export default Button;
버튼 컴포넌트는 clickFunc라는 함수를 받아 클릭 시 실행하도록 되어 있습니다. 또한, 기본 레이블은 'Click Me'로 설정되어 있습니다.
스타일 추가하기
버튼의 스타일을 정의하는 CSS 파일도 만들어봅시다.
/* Button.css */
.Button {
background-color: slateblue;
padding: 10px 30px;
border: none;
border-radius: 10px;
color: white;
transition: opacity 0.2s;
}
.Button:hover {
opacity: 0.7;
}
버튼에 기본적인 스타일과 호버 효과를 추가했습니다.
부모 컴포넌트에서 상태 업데이트 함수 정의하기
이제, 버튼을 사용할 부모 컴포넌트에서 상태를 업데이트하는 함수를 정의해봅시다.
// LuckyN.jsx
import { useState } from "react";
import { getRolls } from "./utils";
import Button from "./Button";
import Dice from "./Dice";
function LuckyN({ title = "Dice Game", numDice = 2, winCheck }) {
const [dice, setDice] = useState(getRolls(numDice));
const isWinner = winCheck(dice);
const roll = () => setDice(getRolls(numDice));
return (
<main className="LuckyN">
<h1>
{title} {isWinner && "You Win!"}
</h1>
<Dice dice={dice} />
<Button clickFunc={roll} label="Re-Roll" />
</main>
);
}
export default LuckyN;
LuckyN 컴포넌트에서는 roll 함수를 정의하고, 이 함수를 Button 컴포넌트에 전달합니다.
정리
이렇게 부모 컴포넌트에서 정의한 상태 업데이트 함수를 자식 컴포넌트로 전달하여 활용하는 방법을 통해, 코드를 재사용하고 컴포넌트 간의 독립성을 유지할 수 있습니다. 함수를 props로 전달하는 이러한 패턴은 리액트에서 자주 사용되며, 코드의 유지보수성과 확장성을 높여줍니다.