componentDidMount()
: 처음 render가 성공적으로 됐다면 실행, 리랜더링이 되면 실행x (비동기 요청)componentDidUpdate()
: 리랜더링 후 실행componentWillUnmount()
: 컴포넌트가 제거되기 직전 실행 (비동기 요청 정리)interval;
componentDidMount() {
this.interval = setInterval(() => {
...
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
setInterval()
, setTimeout()
은 없애주지 않으면 메모리 누수가 생기므로 완료되지 않은 비동기 요청은 componentWillUnmount()에서 정리해주기
❗ 비동기 안에서 바깥에 있는 함수를 참조하면 클로저 문제 발생 항상 주의
import React, { Component } from 'react';
const rspCoords = {
바위: '0',
가위: '-260px',
보: '-538px'
};
const scores = {
가위: 1,
바위: 0,
보: -1,
};
const computerChoice = (imgCoord) => {
return Object.entries(rspCoords).find(function (v) {
return v[1] === imgCoord;
})[0];
};
class RSP extends Component {
state = {
result: '',
imgCoord: '0',
score: 0,
};
interval;
componentDidMount() {
this.interval = setInterval(this.changeHand, 100);
}
componentWillUnmount() {
clearInterval(this.interval);
}
changeHand = () => {
const { imgCoord } = this.state;
if (imgCoord === rspCoords.바위) {
this.setState({
imgCoord: rspCoords.가위,
});
} else if (imgCoord === rspCoords.가위) {
this.setState({
imgCoord: rspCoords.보,
});
} else if (imgCoord === rspCoords.보) {
this.setState({
imgCoord: rspCoords.바위,
});
}
}
// 버튼 클릭 시 인터벌 잠시 멈추고 점수 계산 후 다시 실행
onClickBtn = (choice) => {
const { imgCoord } = this.state;
clearInterval(this.interval);
const myScore = scores[choice];
const cpuScore = scores[computerChoice(imgCoord)];
const diff = myScore - cpuScore; // 내 스코어와 컴퓨터 스코어 빼기
if (diff === 0) { // 차이가 없으면
this.setState({
result: '비겼습니다!',
});
} else if ([-1, 2].includes(diff)) {
this.setState((prevState) => {
return {
result: '이겼습니다!',
score: prevState.score + 1,
};
});
} else {
this.setState((prevState) => {
return {
result: '졌습니다!',
score: prevState.score - 1,
};
});
}
setTimeout(() => {
this.interval = setInterval(this.changeHand, 100);
}, 2000); // 결과 나온 후 2초 기다렸다가 다시 돌리기
};
render() {
const { result, score, imgCoord } = this.state;
return (
<>
<div id="computer" style={{ background: `url(https://data.ac-illust.com/data/thumbnails/4f/4f63b32d7d43ea2cb231c0724200cf8e_t.jpeg) ${imgCoord} 0` }}></div>
<div>
<button id="rock" className="btn" onClick={() => this.onClickBtn('바위')}>바위</button>
<button id="scissor" className="btn" onClick={() => this.onClickBtn('가위')}>가위</button>
<button id="paper" className="btn" onClick={() => this.onClickBtn('보')}>보</button>
</div>
<div>{result}</div>
<div>현재 {score}점</div>
</>
);
}
}
export default RSP;
메서드 안에 함수를 호출하는 부분이 있으면 화살표 함수를 빼서 함수를 연달아 쓸 수 있음
onClickBtn = (choice) => () => {
...
};
...
render() {
return (
<button onClick={this.onClickBtn('바위')}>바위</button>
<button onClick={this.onClickBtn('가위')}>가위</button>
<button onClick={this.onClickBtn('보')}>보</button>
);
}
Hooks엔 LifeCycle이 없지만 useEffect로 동일하게 동작하게 할 수 있음
useEffect(() => { // componentDidMount, componentDidUpadate 역할 (1대1 대응은 아님)
interval.current = setInterval(changeHand, 100);
return () => { //componentWillUnmount 역할
clearInterval(interval.current);
}
}, [imgCoord]);
//두번째 인수 배열에 넣은 값(imgCoord)이 바뀔 때 useEffect 실행 (useEffect를 다시 실행할 값만 넣어주기)
매번 clearInterval()
을 하기 때문에 setTimeout()
하는 것과 동일
👉 state가 업데이트 될 때 마다 useEffect가 실행 후 종료가 되므로
setTimeout()
을 사용해도 됨!