PureComponent
나 함수 컴포넌트(+ memo
)로 만드는 것이 좋음Lotto.jsx
import React, { Component } from 'react';
function getWinNumbers() {
// 1~ 45가 들어있는 배열 생성
const candidate = Array(45).fill().map((v, i) => i + 1);
const shuffle = [];
// 1~45를 랜덤하게 섞기
while (candidate.length > 0) {
shuffle.push(candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0]);
}
// shuffle의 마지막 수를 보너스 숫자로
const bonusNumber = shuffle[shuffle.length - 1];
// shuffle의 0~5번째 수를 오름차순 정렬하여 당첨 숫자로
const winNumbers = shuffle.splice(0, 6).sort((p, c) => p - c);
return [...winNumbers, bonusNumber];
}
class Lotto extends Component {
state = {
winNumbers: getWinNumbers(), // 당첨 숫자
winBalls: [],
bonus: null, // 보너스공
redo: false,
};
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;
Ball.jsx
import React, {memo} from 'react';
import Ball from './Ball';
// 함수 컴포넌트
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;
componentDidMount
에서 setTimeout
사용let
을 사용하면 클로저 문제가 발생하지 않음 (ES6부터 개선된 부분)componentWillUnmount
에서 clearTimeout
하여 메모리 누수 방지Lotto.jsx
...
timeouts = [];
componentDidMount() {
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); // 1초 간격으로 출력
}
this.timeouts[6] = setTimeout(() => { // 보너스공
this.setState({
bonus: winNumbers[6],
redo: true,
}, 7000);
});
}
componentWillUnmount() {
this.timeouts.forEach((v) => {
clearTimeout(v);
});
}
// 초기화
onClickRedo = () => {
this.setState({
winNumbers: getWinNumbers(), // 당첨 숫자
winBalls: [],
bonus: null, // 보너스공
redo: false,
});
this.timeouts = [];
};
...
Lotto.jsx
...
timeouts = [];
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); // 1초 간격으로 출력
}
this.timeouts[6] = setTimeout(() => { // 보너스공
this.setState({
bonus: winNumbers[1],
redo: true,
});
}, 7000);
};
componentDidMount() {
this.runTimeouts();
}
componentDidUpdate(prevProps, prevState) {
// winBalls가 세팅되지 않은 경우 (한번더로 초기화 한 후)
if (this.state.winBalls.length === 0) {
this.runTimeouts();
}
}
...
useEffect
의 두 번째 인자가 componentDidMount
와 동일componentDidMount
, componentDidUpdate
둘 다 수행import React, { useState, useRef, useEffect } from 'react';
import Ball from './Ball';
function getWinNumbers() {
console.log('getWinNumbers');
const candidate = Array(45).fill().map((v, i) => i + 1);
const shuffle = [];
while (candidate.length > 0) {
shuffle.push(candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0]);
}
const bonusNumber = shuffle[shuffle.length - 1];
const winNumbers = shuffle.splice(0, 6).sort((p, c) => p - c);
return [...winNumbers, bonusNumber];
}
const Lotto = () => {
const [winNumbers, setWinNumbers] = useState(getWinNumbers());
const [winBalls, setWinBalls] = useState([]);
const [bonus, setBonus] = useState(null);
const [redo, setRedo] = useState(false);
const timeouts = useRef([]);
useEffect(() => {
for(let i = 0 ; i < winNumbers.length - 1; i++){
timeouts.current[i] = setTimeout(() => {
setWinBalls((prevBalls) => [...prevBalls, winNumbers[i]]);
}, (i + 1) * 1000); // 1초 간격으로 출력
}
timeouts.current[6] = setTimeout(() => { // 보너스공
setBonus(winNumbers[1]);
setRedo(true);
}, 7000);
return () => {
timeouts.current.forEach((v) => {
clearTimeout(v);
});
}
}, [timeouts.current]); // winBalls.length === 0으로 설정하면 처음 실행부터 적용되므로 중복된 숫자가 2번 나옴
const onClickRedo = () => { // 초기화
setWinNumbers(getWinNumbers());
setWinBalls([]);
setBonus(null);
setRedo(false);
timeouts.current = [];
};
return (
<>
<div>당첨 숫자</div>
<div id="결과창">
{winBalls.map((v) => <Ball key={v} number={v}/>)}
</div>
<div>보너스</div>
{bonus && <Ball number={bonus}/>}
{redo && <button onClick={onClickRedo}>한 번 더!</button>}
</>
);
}
export default Lotto;
getNumbers
가 반복 실행되지 않도록 실행한 결과값을 임의로 저장해두는 데 사용Lotto.jsx
const Lotto = () => {
const lottoNumbers = useMemo(() => getWinNumbers(), []);
const [winNumbers, setWinNumbers] = useState(lottoNumbers);
...
useCallback
사용 필수useCallback
에서 쓰이는 state를 두 번째 인자에 넣어주어야 변경을 감지Lotto.jsx
const onClickRedo = useCallback(() => { // 초기화
setWinNumbers(getWinNumbers());
setWinBalls([]);
setBonus(null);
setRedo(false);
timeouts.current = [];
}, [winNumbers]);
useEffect
에서 componentDidUpdate
기능만 사용하고 싶은 경우 const mounted = useRef(false);
useEffect(() => {
if(!mounted.current){
mounted.current = true;
}else{
// ajax
}
}, [바뀌는값]);