
componentWillUnmount를 이용하여 정리해 주어야 한다. componentWillUnmount() {
this.timeouts.forEach((v) => {
clearTimeout(v);
})
}
import React, { Component } 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.slice(0, 6).sort((p, c) => p - c);
return [...winNumbers, bonusNumber];
}
class Lotto extends Component {
state = {
winNumbers: getWinNumbers(), // 당첨 숫자들
winBalls: [],
bonus: null, // 보너스 공
redo: false,
};
timeouts = [];
runTimeouts = () => {
console.log('runtimeOuts');
const { winNumbers } = this.state;
for (let i = 0; i < this.state.winNumbers.length - 1; i++) {
// let을 사용하면 클로저 문제 X
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() {
// 컴포넌트가 렌더링되자마자 진행
console.log('didMount');
this.runTimeouts();
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('didUpdate');
if(this.state.winBalls.length === 0) {
// winBalls의 길이가 0이 될 때 componentDidUpdate 실행
// 조건문으로 감싸지 않으면 매번 실행됨
this.runTimeouts();
}
}
componentWillUnmount() {
this.timeouts.forEach((v) => {
clearTimeout(v);
})
}
onClickRedo = () => { // state 초기화
console.log('onClickRedo');
this.setState({
winNumbers: getWinNumbers(), // 당첨 숫자들
winBalls: [],
bonus: null, // 보너스 공
redo: false,
});
this.timeouts = [];
}
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;
useEffect를 사용하여 componentDidUpdate 기능을 구현하는 것이 주 목적이다.
import React, { useState, useRef, useEffect } from 'react';
import Ball from './ball';
function getWinNumbers() {
// state를 사용하지 않는 함수는 분리
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.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++) {
// let을 사용하면 클로저 문제 X
timeouts.current[i] = setTimeout(() => {
setWinBalls((prevBalls) => [...prevBalls, winNumbers[i]]);
}, (i + 1) * 1000);
}
timeouts.current[6] = setTimeout(() => {
setBonus(winNumbers[6]);
setRedo(true);
}, 7000);
return () => {
timeouts.current.forEach((v) => {
clearTimeout(v);
});
}
}, [timeouts.current]); // input이 빈 배열이면 componentDidMount와 동일
// 배열에 요소가 있으면 componentDidMount와 componentDidUpdate 둘 다 수행
// componentWillUnmount 는 return
const onClickRedo = () => { // state 초기화
console.log('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;

3번 항목까지 진행하였을 때 콘솔 창을 보면, getWinNumbers가 연속으로 실행되고 있다. Hooks의 특성 때문에, 함수 전체가 처음부터 다시 실행되는 현상이 반복되기 때문이다.
이때 사용하는 것이 useMemo이다. 함수를 다시 실행시키지 않고 기억할 수 있게끔 한다.

두번째 요소가 바뀌기 전까지는 계속 같은 값을 기억한다.

Hooks가 getWinNumbers의 리턴값을 기억하고 있으므로, getWinNumbers는 한 번만 실행된다.
useMemo : 복잡한 함수의 결과값을 기억
useRef : 일반 값을 기억
앞서 배운 useMemo가 값(함수의 리턴값)을 기억하는 Hooks이라면, useCallback은 함수 자체를 기억하는 것이다.

이와 같이 onClickRedo 함수를 useCallback으로써 기억하면, 함수 컴포넌트가 재실행되더라도 onClickRedo가 새로 생성되지 않는다.
자식 컴포넌트에 props로 함수를 넘겨줄 때에는 useCallback을 사용하는 것이 좋다. 그렇지 않으면 렌더링할 때마다 매번 새로운 함수가 생성된다.