리액트는 ReactDOM을 통해 렌더링이 되어 jsx를 돔에 붙여주는 순간에 특정한 동작을 할 수 있도록 라이프사이클을 제공한다. 주로 사용하는 라이프 사이클은 아래와 같다.
componentDidMount
컴포넌트가 첫 렌더링이 된 후 최초 1회만 실행, 리렌더링 시에는 동작하지 않는다.
componentWillUnmount
컴포넌트가 제거되기 직전 실행된다.
componentDidUpdate
컴포넌트가 리렌더링 되었을 때 실행된다.
위의 라이프사이클을 기준으로 클래스형 컴포넌트의 경우 아래와 같은 주기를 가진다.
초기
constructor → render → ref rendering → componentDidMount
setState / props 바뀔 때
shouldComponentUpdate(true) → render → componentDidUpdate
부모가 나를 없앴을 때
componentWillUnmount → 소멸
setInterval과 같은 비동기 요청은 보통 componentDidMount에서 많이 한다. 또한 리액트에서는 해당 요청 비동기에 대한 종료를 따로 해주지 않기 때문에 componentWillUnmount에서 해당 비동기 요청을 정리해준다.
만약 setInterval이나 setTimeout 등의 완료되지 않은 비동기 요청을 별도로 정리해주지 않으면 이 모두가 메모리를 소요하는 것들이므로 나중에 터질 위험성이 있으니 유의하자
// RSP.jsx
import React, { Component } from "react";
const rspCoords = {
바위: "0",
가위: "-142px",
보: "-284px",
};
const scores = {
가위: 1,
바위: 0,
보: -1,
};
class RSP extends Component {
state = {
result: "",
imgCoord: "0",
score: 0,
};
interval;
componentDidMount() {
// 1. 비동기 요청
this.interval = setInterval(() => {
// 2. 변수 선언
const { imgCoord } = this.state;
if (imgCoord === rspCoords.바위) {
this.setState({
imgCoord: rspCoords.가위,
});
} else if (imgCoord === rspCoords.가위) {
this.setState({
imgCoord: rspCoords.보,
});
} else {
this.setState({
imgCoord: rspCoords.바위,
});
}
}, 1000);
}
componentWillUnmount() {
// 3. 비동기 요청 정리
clearInterval(this.interval);
}
onClickBtn = (choice) => {};
render() {
const { result, score, imgCoord } = this.state;
return (
<>
<div
id="computer"
style={{ background: `url(<https://en.pimg.jp/023/182/267/1/23182267.jpg>) ${imgCoord} 0` }}
></div>
{/* ... */}
</>
);
}
}
export default RSP;
class형 컴포넌트로 가위바위보 게임 만들기
import React, { Component } from "react";
const rspCoords = {
바위: "0",
가위: "-142px",
보: "-284px",
};
const scores = {
가위: 1,
바위: 0,
보: -1,
};
const computerChoice = (imgCoord) => Object.entries(rspCoords).find((v) => v[1] === imgCoord)[0];
class RSP extends Component {
state = {
result: "",
imgCoord: "0",
score: 0,
};
interval;
// 컴포넌트가 첫 렌더링된 후,(최초 1회) : 비동기 요청
componentDidMount() {
this.interval = setInterval(this.changeHand, 100);
}
// 컴포넌트가 제거되기 직전: 요청한 비동기 요청 정리
componentWillUnmount() {
clearInterval(this.interval);
}
// setInteval 상세 동작
changeHand = () => {
const { imgCoord } = this.state;
if (imgCoord === rspCoords.바위) {
this.setState({
imgCoord: rspCoords.가위,
});
} else if (imgCoord === rspCoords.가위) {
this.setState({
imgCoord: rspCoords.보,
});
} else {
this.setState({
imgCoord: rspCoords.바위,
});
}
};
onClickBtn = (choice) => {
const { imgCoord } = this.state;
// setInterval 리셋
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,
};
});
}
// 2초 간 결과 확인 후 재 실행
setTimeout(() => {
this.interval = setInterval(this.changeHand, 100);
}, 2000);
};
render() {
const { result, score, imgCoord } = this.state;
return (
<>
<div
id="computer"
style={{ background: `url(<https://en.pimg.jp/023/182/267/1/23182267.jpg>) ${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;