setTimeout으로 무작위로 생성한 7개의 숫자를 순서대로 노출하는 컴포넌트를 만들어본다.
function getWinNumbers() {
// 0 - 45까지 들어있는 배열 생성
const candidate = Array(45).fill().map((v, i) => i + 1);
// 랜덤 숫자를 넣는 빈 배열 생성
const shuffle = [];
// candidate가 빈배열이 될 때까지 아래 코드를 동작시킨다.
while (candidate.length > 0){
// 랜덤한 값을 shuffle에 push
shuffle.push(candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0]);
}
// 맨 마지막 bonusNumber 가져오기
const bonusNumber = shuffle[shuffle.length - 1];
// 0 - 6번쨰 숫자를 가져와 오름차순으로 정렬
const winNumbers = shuffle.slice(0, 6).sort((p, c) => p - c);
// 값 리턴
return [...winNumbers, bonusNumber];
}
import React, { memo } from "react";
// 컴포넌트를 다른 컴포넌트로 감싸주는 것을 HOC(high order component)라고 한다.
const Ball = memo(({ number }) => {
let background;
if (number <= 10) {
background = "red";
} else if (number <= 20) {
background = "orange";
} else if (number <= 30) {
background = "yellow";
} else if (number <= 40) {
background = "blue";
} else {
background = "green";
}
return (
<div className="ball" style={{ background }}>
{number}
</div>
);
});
export default Ball;
가장 마지막에 오는 자식 컴포넌트의 경우 보통 PureComponent나 memo를 넣어 불필요한 리렌더링을 방지해준다. 위 코드처럼 컴포넌트를 다른 컴포넌트로 감싸주는 방식을 HOC(high order component)라고 하며, 위와 같은 함수형 컴포넌트는 PureComponent가 아니기 때문에 별도 memo 메서드로 감싸주어야 PureComponent 역할을 한다.
이번 게임에서는 그동안 배웠던 조건문과 반복문을 통 틀어서 사용해볼 수 있다.
import React, { Component } from "react";
import Ball from "./Ball";
function getWinNumbers() { ... }
class Lotto extends Component {
state = {
winNumbers: getWinNumbers(), // 당첨 숫자
winBalls: [],
bonus: null, // 보너스 공
redo: false,
};
timeouts = [];
componentDidMount() {
const { winNumbers } = this.state;
// 1. let을 사용하면 클로저 문제가 발생하지 않는다.
for (let i = 0; i < winNumbers.length - 1; i++) {
// 2. setTimeout 여러번 사용하기
this.timeouts[i] = setTimeout(
() =>
this.setState((prevState) => {
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
}),
(i + 1) * 1000
);
}
// 2. setTimeout 여러번 사용하기: bonus 점수 노출
this.timeouts[6] = setTimeout(
() =>
this.setState({
bonus: winNumbers[6],
redo: true,
}),
7000
);
}
componentWillUnmount() {
// 3. 반드시 setTimeout을 clear해줘야 한다.
this.timeouts.forEach((t) => clearTimeout(t));
}
onClickRedo = () => {};
render() {
const { winBalls, bonus, redo } = this.state;
return (
<>
<div>당첨 숫자</div>
<div id="결과창">
{winBalls.map((v) => (
<Ball key={v} number={v} />
))}
</div>
<div>보너스!</div>
{bonus && <Ball number={bonus} />}
{redo && <button onClick={this.onClickRedo}>한번 더!</button>}
</>
);
}
}
export default Lotto;
import React, { Component } from "react";
import Ball from "./Ball";
function getWinNumbers() { ... }
class Lotto extends Component {
state = { ... };
timeouts = [];
// 1. setTimeout 동작부분 분리
runTimeouts = () => {
const { winNumbers } = this.state;
for (let i = 0; i < winNumbers.length - 1; i++) {
this.timeouts[i] = setTimeout(
() =>
this.setState((prevState) => {
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
}),
(i + 1) * 1000
);
}
this.timeouts[6] = setTimeout(
() =>
this.setState({
bonus: winNumbers[6],
redo: true,
}),
7000
);
};
componentDidMount() {
// 2. setTimeout 실행
this.runTimeouts();
}
componentWillUnmount() {
this.timeouts.forEach((t) => clearTimeout(t));
}
componentDidUpdate(prevProps, prevState) {
// 4. 변경되는 시점 체크 (!this.state.bonus or !this.state.redo 가능)
if (!this.state.winBalls.length) {
// setTimeout 실행
this.runTimeouts();
}
}
onClickRedo = () => {
// 3. 초기화 시켜준다.
this.setState({
winNumbers: getWinNumbers(),
winBalls: [],
bonus: null,
redo: false,
});
this.timeouts = [];
};
render() {
return ( {/* */} );
}
}
export default Lotto;
클래스형 컴포넌트를 Hooks로 바꿔보자.