리액트 주사위게임&가위바위보게임 만들기 - 2.컴포넌트
1️⃣ 하나의 컴포넌트 만들기 - Board.js
import { useState } from "react";
import Button from "./Button";
import Dice from "./Dice";
function random(n) {
return Math.ceil(Math.random() * n);
} // 파라미터 n으로 숫자값을 전달 받아서 1부터 n까지의 랜덤한 정수를 반환하는 함수
function Board({name, color}) {
const [num, setNum] = useState(1);
const [sum, setSum] = useState(0); //총점 구하는 state -> 초기값 0으로 줌
const [gameHistory, setGameHistory] = useState([]); // 빈배열을 초기값으로 받는 state
const handleRollClick = () => {
const nextNum = random(6);
setNum(nextNum);
setSum(sum + nextNum); // 총점
// gameHistory.push(nextNum); //gameHistory가 배열이니까 push 메소드로 nextNum을 추가하고
setGameHistory([...gameHistory, nextNum]); //새 값이 추가된 gameHistory State를 전달
}; //num state를 바꾸는 함수
const handleClearClick = () => {
setNum(1);
setSum(0);
setGameHistory([]);
}; //초기화 기능
return (
<div>
<div>
<Button onClick={handleRollClick}>던지기</Button>
<Button onClick={handleClearClick}>처음부터</Button>
</div>
<div>
<h2>{name}</h2>
<Dice color={color} num={num} />
<h2>총점</h2>
<p>{sum}</p>
<h2>기록</h2>
<p>{gameHistory.join(", ")}</p>
</div>
</div>
);
}
export default Board;
2️⃣ 공통된 데이터(자식 컴포넌트)를 부모(App.js)한테 올려주기 (State Lifting)
import Board from "./Board";
import { useState } from "react";
import Button from "./Button";
function random(n) {
return Math.ceil(Math.random() * n);
} // 파라미터 n으로 숫자값을 전달 받아서 1부터 n까지의 랜덤한 정수를 반환하는 함수
function App() {
const [num, setNum] = useState(1);
const [sum, setSum] = useState(0); //총점 구하는 state -> 초기값 0으로 줌
const [gameHistory, setGameHistory] = useState([]); // 빈배열을 초기값으로 받는 state
const [othernum, setOtherNum] = useState(1);
const [othersum, setOtherSum] = useState(0); //총점 구하는 state -> 초기값 0으로 줌
const [othergameHistory, setOtherGameHistory] = useState([]); // 빈배열을 초기값으로 받는 state
const handleRollClick = () => {
const nextNum = random(6);
const nextOtherNum = random(6);
setNum(nextNum);
setSum(sum + nextNum); // 총점
// gameHistory.push(nextNum); //gameHistory가 배열이니까 push 메소드로 nextNum을 추가하고
setGameHistory([...gameHistory, nextNum]); //새 값이 추가된 gameHistory State를 전달
setOtherNum(nextOtherNum);
setOtherSum(sum+nextOtherNum);
setOtherGameHistory([...othergameHistory, nextOtherNum]);
}; //num state를 바꾸는 함수
const handleClearClick = () => {
setNum(1);
setSum(0);
setGameHistory([]);
setOtherNum(1);
setOtherSum(0);
setOtherGameHistory([]);
}; //초기화 기능
return (
<div>
<div>
<div>
<Button onClick={handleRollClick}>던지기</Button>
<Button onClick={handleClearClick}>처음부터</Button>
</div>
<Board name="나" color="blue" />
<Board name="상대" color="red" />
</div>
</div>
);
}
export default App;
3️⃣ state를 각 Board 컴포넌트의 적절한 prop으로 전달해주기
<Board name="나" color="blue" num={num} sum={sum} gameHistory={gameHistory} />
<Board name="상대" color="red" num={othernum} sum={othersum} gameHistory={othergameHistory} />
: 상대의 Board 컴포넌트와 prop 이름은 똑같지만, 전달하는 state값을 상대편 state값으로 전달해 줌!
4️⃣ 값들을 컴포넌트에서 전달받기
🟡변경 전
import Dice from "./Dice";
function Board({name, color}) {
return (
<div>
<h2>{name}</h2>
<Dice color={color} num={num} />
<h2>총점</h2>
<p>{sum}</p>
<h2>기록</h2>
<p>{gameHistory.join(", ")}</p>
</div>
);
}
export default Board;
🔴변경 후
import Dice from "./Dice";
function Board({name, color, num, sum, gameHistory}) {
return (
<div>
<h2>{name}</h2>
<Dice color={color} num={num} />
<h2>총점</h2>
<p>{sum}</p>
<h2>기록</h2>
<p>{gameHistory.join(", ")}</p>
</div>
);
}
export default Board;
5️⃣ 코드 정리하기
① 현재 주사위의 숫자값 = 기록의 마지막 값
② 총점 = 기록이 가지고 있는 모든 숫자의 합
⭐ 주사위를 던진 기록만 있으면 총점과 현재 주사위의 숫자값을 구할 수 있음
- App.js
import Board from "./Board";
import { useState } from "react";
import Button from "./Button";
function random(n) {
return Math.ceil(Math.random() * n);
} // 파라미터 n으로 숫자값을 전달 받아서 1부터 n까지의 랜덤한 정수를 반환하는 함수
function App() {
const [mygameHistory, setMyGameHistory] = useState([]); // 빈배열을 초기값으로 받는 state
const [othergameHistory, setOtherGameHistory] = useState([]); // 빈배열을 초기값으로 받는 state
const handleRollClick = () => {
const nextMyNum = random(6);
const nextOtherNum = random(6);
// gameHistory.push(nextNum); //gameHistory가 배열이니까 push 메소드로 nextNum을 추가하고
setMyGameHistory([...mygameHistory, nextMyNum]); //새 값이 추가된 gameHistory State를 전달
setOtherGameHistory([...othergameHistory, nextOtherNum]);
}; //num state를 바꾸는 함수
const handleClearClick = () => {
setMyGameHistory([]);
setOtherGameHistory([]);
}; //초기화 기능
return (
<div>
<div>
<div>
<Button onClick={handleRollClick}>던지기</Button>
<Button onClick={handleClearClick}>처음부터</Button>
</div>
<Board name="나" color="blue" gameHistory={mygameHistory} />
<Board name="상대" color="red" gameHistory={othergameHistory} />
</div>
</div>
);
}
export default App;
- Board.js
import Dice from "./Dice";
function Board({name, color, gameHistory}) {
const num = gameHistory[gameHistory.length -1] || 1;
const sum = gameHistory.reduce((a,b) => a+b, 0);
return (
<div>
<h2>{name}</h2>
<Dice color={color} num={num} />
<h2>총점</h2>
<p>{sum}</p>
<h2>기록</h2>
<p>{gameHistory.join(", ")}</p>
</div>
);
}
export default Board;
⭐ num과 sum을 직접 계산해서 변수로 사용함
① num 계산
🔎 a || b (OR)
: a가 true값인 경우 b는 무시되고 a가 결과값으로 나오고, 반대로 a가 false값인 경우 b가 결과값으로 나옴
➡ 피연산자가 모두 false인 경우를 제외하고 연산 결과는 항상 true가 나옴
💡 false값의 예시
- null
- NaN
- 0
- 빈 문자열
- undefined
const num = gameHistory[gameHistory.length -1] || 1;
: [gameHistory.length -1] = gameHistory 배열의 마지막 값을 받아오는 코드
➡ 만약, gamehistory가 빈 배열이면 undefined가 나옴 = undefined || 1 = 1
➡ 반대로, gamehistory에 값이 1개 이상 있으면 배열의 마지막 값이 num에 들어감!
② sum 계산
🔎 .reduce()
: 배열의 각 요소에 대해 함수를 실행하고 누적된 값을 출력
➡ 가장 기본적인 예제로는, 모든 배열의 합을 구하는 경우!
const sum = gameHistory.reduce((a,b) => a+b, 0);
- initialValue값을 0으로 두었기 때문에 (a,b)의 초기값은 0이 됨
- 배열의 첫 번째 요소부터 a와 b를 더해감 = a+b
✅ state가 바뀌었을 때 리액트가 렌더링하는 방식 = Virtual DOM(가상 DOM) 활용!
: 엘리먼트를 새로 렌더링할 때, 리액트는 그 모습을 실제 DOM 트리에 바로 반영하는게 아니라 일단 Virtual DOM에 적용함
① 트리를 지우고 새로 만들지만 실제 DOM 트리에 바로 반영하는것이 아님
② 리액트가 State 변경 전의 Virtual DOM과 변경 후의 Virtual DOM을 비교함
③ 바뀐 부분만 찾아낸 다음에, 각각에 해당하는 실제 DOM 노드를 변경함
==> 단순하고 깔끔한 코드를 작성할 수 있고, 무슨 데이터를 어떻게 보여 줄 것인지만 신경 쓰면 되는 장점이 있음!
==> 리액트를 Virtual DOM이 바뀔 때마다 브라우저에 바로 전달하지 않고, 변경 사항들을 모아 뒀다가 적당히 나눠서 브라우저에 전달함(=한정된 브라우저 자원을 효율적으로 활용할 수 있게 됨!)