일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- JavaScript
- 디자인패턴
- 웹개발
- 하프가드
- 리액트
- 주짓수
- 개발
- 개발자
- 프로그래밍
- Express
- Node
- 솔로드릴
- Redux
- 주짓떼라
- web
- 클로즈가드
- 파이썬
- 주짓떼로
- nodejs
- REACT
- 노드
- 자바스크립트
- 영화
- development
- 영화감상
- git
- graphQL
- 영화리뷰
- 드릴
- 엄티로드
- Today
- Total
As i wish
[React 가위, 바위, 보] 라이프 사이클 본문
오늘은 가위바위보 게임을 바탕으로 React에 라이프 사이클, 고차 함수, Hooks useEffect에 대하여 포스팅 해보겠습니다.
계속 해서 언급 하지만 제 포스팅은 '제로초' 님의 리액트 강좌를 바탕으로 합니다.
먼저 라이프사이클에 대하여 알아보겠습니다.
리액트 라이프 사이클은 하나의 component의 생명 주기를 의미 하는데요.
처음 component가 rendering 되고 없어 질 때 까지 를 의미 합니다.
여러가지가 있는데, 기본적으로
componentDidMount, componentDidUpdate, componentWillUnMount
요 3가지가 가장 중요하죠.
대략적인 의미는 다음과 같습니다.
componentDidMount: render 가 실행되고 그 다음에 실행, 리랜더링 일어날 시에는 실행 안됨. Component 가 첫 렌더링 된 후
- 비동기 요청을 많이 한다.
* componentWillUnmount: Component 가 제거되기 직전
- 비동기 요청 정리 한다.
* componentDidUpdate: 리랜더링 후 실행
더 자세하게 알고 싶으면 다른 블로그를 참조 해주세요.
무튼 이름만 봐도 쉽게 무슨 의미인지는 알 수 있죠.
기본적인 순서를 보자면
constructor -> render -> ref -> componentDidMount -> (setState/props changed), shouldComponentUpdaet(true) -> render -> componentDidUpdate -> (부모가 없앨 때) componentWillUnmount -> 소멸
이런식이라고 보시면 됩니다.
오늘 만들어 볼 게임에 대한 결과부터 보겠습니다.
이렇게 그림이 가위, 바위, 보 순서로 계속해서 변화하고, 사용자가 밑에 버튼을 눌러서 이겼는지, 졌는지, 비겼는지를 알아보는 단순한 게임이죠.
프로젝트 셋팅 및 webpack, hot loader 적용
오늘은 Class, hooks 를 각각 RcpGame.jsx, RcpGameHook.jsx 로 만들어 보겠습니다.
RcpGame.jsx
import React, { Component } from 'react';
// 클래스의 경우 -> constructor -> render -> ref -> componentDidMount
// (setState/props 바뀔때) -> shouldComponentUpdate(true) -> render -> componentDidUpdate
// 부모가 나를 없앴을 때 -> componentWillUnmount -> 소멸
const rspCoords = {
rock: '0',
cissor: '-142px',
paper: '-284px',
};
const scores = {
rock: 1,
cissor: 0,
paper: -1,
};
const computerChoice = (imageCoord) => {
return Object.entries(rspCoords).find((value) => {
return value[1] === imageCoord;
})[0];
};
class RcpGame extends Component {
state = {
imageCoord: rspCoords.rock,
result: '',
score: 0,
};
interval;
componentDidMount() {
this.interval = setInterval(this.changeHand, 100);
}
componentDidUpdate () {
// Nothing
}
componentWillUnMount() {
clearInterval(this.interval);
}
changeHand = () => {
const { imageCoord } = this.state;
if (imageCoord === rspCoords.rock) {
this.setState({
imageCoord: rspCoords.cissor
});
} else if (imageCoord === rspCoords.cissor) {
this.setState({
imageCoord: rspCoords.paper
});
} else if (imageCoord === rspCoords.paper) {
this.setState({
imageCoord: rspCoords.rock
});
}
};
onClickBtn = (choice) => () => {
const { imageCoord } = this.state;
clearInterval(this.interval);
const userScore = scores[choice];
const compScore = scores[computerChoice(imageCoord)];
const scoreDiff = userScore - compScore;
if (scoreDiff === 0) {
this.setState({
result: 'Draw, Select: ' + choice
});
} else if (scoreDiff === 1 || scoreDiff === -2) {
this.setState((prevState) => {
return {
result: 'Win, Select: ' + choice,
score: prevState.score + 1
};
});
} else {
this.setState((prevState) => {
return {
result: 'Loose, Select: ' + choice,
score: prevState.score - 1
};
});
}
setTimeout(() => {
this.interval = setInterval(this.changeHand, 100);
}, 1000);
}
render() {
const { imageCoord, result, score } = this.state;
return (
<>
<div id="computer" style={{ backgroundImage: 'url(https://en.pimg.jp/023/182/267/1/23182267.jpg)', backgroundPosition: `${imageCoord} 0`}} />
<div>
<button id="rock" className="btn" onClick={this.onClickBtn('rock')}>바위</button>
<button id="scissor" className="btn" onClick={this.onClickBtn('cissor')}>가위</button>
<button id="paper" className="btn" onClick={this.onClickBtn('paper')}>보</button>
</div>
<div>{result}</div>
<div>현재 {score}점</div>
</>
);
}
}
export default RcpGame;
자세한 코드 설명은 주석으로 대체 하였지만, 코드에서 보심과 같이 componentDidMount 에서 interval 를 통해 그림이 계속 바뀌도록 했고 만약 component의 생명이 끝나게 되면 componentWillUnmount 를 통하여 interval를 제거 해줬습니다.
만약 interval를 제거해주지 않는다면, 계속해서 interval이 살아있어서 2개, 3개, 4개 등 무한대로 interval이 돌겠죠?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>RcpGame</title>
<style>
#computer {
width: 142px;
height: 200px;
background-position: 0 0;
}
</style>
<style>
</style>
</head>
<body>
<div id="root"></div>
<!-- 웹팩으로 빌드한 ./dist/app.js 파일 -->
<!-- <script src="./dist/app.js"></script> -->
<!-- webpack-dev-server 사용시 dist 폴더 사용 안함 -->
<!-- but webpack.config.js 에서 publicPath 사용시 dist 폴더 사용 가능 -->
<script src="./app.js"></script>
</body>
</html>
간단한 스타일이 들어가 있는 index.html
client.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { hot } from 'react-hot-loader/root';
// import RcpGame from './RcpGame'; // Class 사용
import RcpGameHook from './RcpGameHook'; // Hooks 사용
// const Hot = hot(RcpGame);
const Hot = hot(RcpGameHook);
ReactDOM.render(<Hot />, document.getElementById('root')); // Class 사용
마지막으로 hooks 로 바꾼 코드 입니다.
RcpGameHook.jsx
import React from 'react';
const { useState, useRef, useEffect } = React;
// 클래스의 경우 -> constructor -> render -> ref -> componentDidMount
// (setState/props 바뀔때) -> shouldComponentUpdate(true) -> render -> componentDidUpdate
// 부모가 나를 없앴을 때 -> componentWillUnmount -> 소멸
const rspCoords = {
rock: '0',
cissor: '-142px',
paper: '-284px',
};
const scores = {
rock: 1,
cissor: 0,
paper: -1,
};
const computerChoice = (imageCoord) => {
return Object.entries(rspCoords).find((value) => {
return value[1] === imageCoord;
})[0];
};
const RcpGame = () => {
const [imageCoord, setImageCoord] = useState(rspCoords.rock);
const [result, setResult] = useState('');
const [score, setScore] = useState(0);
const interval = useRef(null);
// 첫 번째 인수는 함수 두 번째 인수는 변수인데, 이 두 번째 인수가 클로저 문제를 해결해 준다.
// 즉, 두 번째 인수 배열에 넣은 값 들이 바뀔 때에 useEffect 가 실행된다.
useEffect(() => { // componentDidMount, componentDidUpdate 역할 (1대1 대응은 아님)
console.log('Restart');
// useRef: Use .current
interval.current = setInterval(changeHand, 500000);
return () => { // componentWillUnmount 역할
console.log('End');
clearInterval(interval.current);
}
}, [imageCoord]);
const changeHand = () => {
if (imageCoord === rspCoords.rock) {
setImageCoord(rspCoords.cissor);
} else if (imageCoord === rspCoords.cissor) {
setImageCoord(rspCoords.paper);
} else if (imageCoord === rspCoords.paper) {
setImageCoord(rspCoords.rock);
}
};
const onClickBtn = (choice) => () => {
clearInterval(interval.current);
const userScore = scores[choice];
const compScore = scores[computerChoice(imageCoord)];
const scoreDiff = userScore - compScore;
if (scoreDiff === 0) {
setResult('Draw, Select: ' + choice);
} else if (scoreDiff === 1 || scoreDiff === -2) {
setResult('Win, Select: ' + choice);
setScore((prevScore) => {
return prevScore + 1;
});
} else {
setResult('Loose, Select: ' + choice);
setScore((prevScore) => {
return prevScore - 1;
});
}
setTimeout(() => {
interval.current = setInterval(changeHand, 100);
}, 1000);
}
return (
<>
<div id="computer" style={{ backgroundImage: 'url(https://en.pimg.jp/023/182/267/1/23182267.jpg)', backgroundPosition: `${imageCoord} 0`}} />
<div>
<button id="rock" className="btn" onClick={onClickBtn('rock')}>바위</button>
<button id="scissor" className="btn" onClick={onClickBtn('cissor')}>가위</button>
<button id="paper" className="btn" onClick={onClickBtn('paper')}>보</button>
</div>
<div>{result}</div>
<div>현재 {score}점</div>
</>
);
};
export default RcpGame;
hooks 에선 component 시리즈를 사용하지 않고 useEffect 를 사용하는데요. 이는 componentDidMount, componentDidUpdate, componentWillUnmount 역할을 한번에 처리 합니다.
다음과 같이 useEffect 에 첫 번째 인수는 함수, 두 번째 인수는 변수인데, 이 배열에 넣은 변수 들이 바뀔 때에 useEffect가 실행 됩니다.
그래서 실제 로그를 찍어 보시면 어떻게 동작하시는지 쉽게 파악 하실 수 있습니다.
RcpGame folder structure
'React JS' 카테고리의 다른 글
[React 틱 택 토] Redux - React 프로젝트 세팅 (0) | 2019.07.18 |
---|---|
[React Lotto] useEffect, useMemo, useCallback (0) | 2019.07.11 |
[React JS] 프로젝트 셋팅 및 webpack, hot loader적용 (0) | 2019.07.08 |
[React 반응속도 체크] 조건문 및 타이머 (0) | 2019.07.04 |
[React 숫자 야구] props 와 component 분리 (0) | 2019.06.21 |