import React, { Component } from 'react'
import Ball from './Ball';
function getWinNumbers() {
console.log('getWinNumbers');
const candidate = Array(45).fill().map((v, i) => i + 1); // 45개의 숫자를 candidate 배열에 넣기
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.slice(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;
import React, { memo } from 'react'
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;
let
사용 시 클로저 문제 발생 xsetTimemout()
사용 시 끝나고 항상 clear 해줘야 함 (메모리 누수 문제 발생)...
timeouts = [];
componentDidMount() {
const { winNumbers } = this.state;
for (let i = 0; i < winNumbers.length - 1; i++) { // 보너스 공 때문에 1 빼줌
this.timeouts[i] = setTimeout(() => {
this.setState((prevState) => {
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
});
}, (i + 1) * 1000); // 순서대로 1초, 2초, 3초 ...
}
this.timeouts[6] = setTimeout(() => {
this.setState({
bonus: winNumbers[6], // 마지막공이 보너스 공
redo: true,
});
}, 7000);
}
componentWillUnmount() {
this.timeouts.forEach((v) => {
clearTimeout(v);
});
}
componentDidUpdate()
를 이용하여 버튼 클릭 시 재실행하게 하기
...
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() {
this.runTimeouts();
}
componentDidUpdate(prevProps, prevState) { // 전 props, 전 state값이 바뀔 때 실행
if (this.state.winBalls.length === 0) { // redo 클릭 시 winBalls는 빈 배열
this.runTimeouts();
}
}
onClickRedo = () => {
this.setState({
winNumbers: getWinNumbers(),
winBalls: [],
bonus: null,
redo: false,
});
this.timeouts = [];
};
Class를 Hooks로 바꾸기
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); // 45개의 숫자를 candidate 배열에 넣기
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.slice(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(() => {
console.log('useEffect');
for (let i = 0; i < winNumbers.length - 1; i++) {
timeouts.current[i] = setTimeout(() => {
setWinBalls((prevBalls) => [...prevBalls, winNumbers[i]]);
}, (i + 1) * 1000);
}
timeouts.current[6] = setTimeout(() => {
setBonus(winNumbers[6]);
setRedo(true);
}, 7000);
return () => { // componentWillUnMount는 return
timeouts.current.forEach((v) => {
clearTimeout(v);
});
};
}, [timeouts.current]); // 빈 배열이면 componentDidMount와 동일
//배열에 요소가 있으면 componentDidMount랑 componentDidUpdate 둘 다 수행
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;
timeouts.current[i] = ...
는 current 배열에 요소를 넣어준거라 바뀌지 않고, timeouts.current = []
는 current에 직접 넣어 값이 바뀜const Lotto = () => {
const lottoNumbers = useMemo(() => getWinNumbers(), []);
const [winNumbers, setWinNumbers] = useState(lottoNumbers);
...
useMemo
는 결과값을 재사용 할 때 사용하지만, useCallback
은 함수 자체를 기억해둬서 재실행되도 새로 생성되지 않고 재사용const onClickRedo = useCallback(() => {
setWinNumbers(getWinNumbers());
setWinBalls([]);
setBonus(null);
setRedo(false);
timeouts.current = [];
}, [winNumbers]);
Hooks에 대한 자잘한 팁들
- Hooks는 순서가 매우 중요!
- 조건문 안에 절대 넣으면 안됨, 함수나 반복문 안에도 웬만하면 넣지 말기