일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 하프가드
- Express
- REACT
- 주짓떼로
- nodejs
- Node
- 개발
- 노드
- git
- 프로그래밍
- Redux
- 솔로드릴
- 클로즈가드
- 개발자
- 엄티로드
- 자바스크립트
- 주짓수
- JavaScript
- 웹개발
- 디자인패턴
- web
- 영화리뷰
- 드릴
- 영화
- 리액트
- 파이썬
- 주짓떼라
- graphQL
- development
- 영화감상
- Today
- Total
As i wish
[React Lotto] useEffect, useMemo, useCallback 본문
안녕하세요. 엄티입니다. 오늘은 Lotto Game 을 만들어 보겠습니다.
제 포스팅은 '제로초' 님의 웹게임 강좌를 기반으로 제가 복습한 형태로 진행 됩니다.
일단 지난번에 라이프사이클에 대하여 배웠는데, 오늘은 조금 더 자세하게 배웠습니다.
일단 기본적인 라이프 사이클은
constructor -> render -> ref -> componentDidMount -> (setState/props changed), shouldComponentUpdaet(true) -> render -> componentDidUpdate -> (부모가 없앨 때) componentWillUnmount -> 소멸
이런식이고요 지난 포스팅을 보시고 싶으신 분은 링크를 눌러주세요.
무튼 오늘은 Hooks에 대하여 더 자세히 알아보고 지난번에 하지 않았던 componentDidUpdate에 대하여 알아봤습니다.
일단 오늘 만들 게임은 다음과 같습니다.
위 처럼 WinBalls 가 6개 나오노 Bonus Ball 이 하나 더 나온 뒤 Redo 버튼이 생겨서 다시 시작할 수 있는 그런 게임이죠.
그럼 코드로 보겠습니다.
자세한 설명은 코드에 해놨으니 확인해 주세요 .
프로젝트 세팅은 늘 동일합니다.
Lotto.jsx
import React, { Component } from 'react';
import Ball from './Ball';
const getWinNumbers =() => {
console.log('Get win numbers');
const candidates = Array(45).fill().map((v, i) => i + 1); // Make 1 ~ 45 Arrays
const shuffled = [];
while(candidates.length > 0) {
shuffled.push(candidates.splice(Math.floor(Math.random() * candidates.length), 1)[0]);
} // Splice candidates Array when candidates length is 0
const winNumbers = shuffled.slice(0, 6); // Slice 6
const bonus = shuffled[shuffled.length - 1]; // Slice last element
return [...winNumbers, bonus]; // Combine two element
}
class Lotto extends Component {
state = {
winNumbers: getWinNumbers(),
winBalls: [],
bonus: null,
redo: false,
}
timeouts = [];
componentDidMount() {
this.setWinBallsTimers(); // Start timer
}
componentDidUpdate (prevProps, prevState) {
console.log('Component did update');
if (this.state.winBalls.length === 0) {
this.setWinBallsTimers(); // When call winBalls state length is 0
}
}
componentWillUnMount() {
this.timeouts.forEach((v) => {
clearTimeout(v); // Clear timers
});
}
setWinBallsTimers = () => {
const { winNumbers } = this.state;
winNumbers.forEach((item, index) => {
this.timeouts = setTimeout(() => {
if (index === winNumbers.length - 1) {
this.setState({
bonus: item,
redo: true
}); // Set bonus ball and set redo(Show Redo button)
} else {
this.setState((prevState) => {
return {
winBalls: [...prevState.winBalls, item]
};
});
}
}, (index + 1) * 500) // Set timeout by index
});
}
setBonusNumber = () => {
const { bonus, redo } = this.state;
// Using if statement
return (
<>
{!!bonus ? <Ball number={bonus}/> : null}
{!!redo ? <button onClick={this.clickRedo}>Redo</button> : null}
</>
)
}
clickRedo = () => {
console.log('Click redo');
this.setState({
winNumbers: getWinNumbers(),
winBalls: [],
bonus: null,
redo: false,
}); // Clear states
this.timeouts = []; // Clear timers
}
render() {
const { winBalls } = this.state;
return (
<>
<div>
WinBalls
</div>
{
winBalls.map((value) => { // Seperate by for loop
return <Ball key={value} number={value}/>
})
}
<div>
Bonus Ball
</div>
{this.setBonusNumber()}
</>
);
}
}
export default Lotto;
Ball.jsx 는 두 가지 방법으로 만들어 봤는데요. 함수형과 Class 형 필요하신걸로 쓰시면 될 것 같네요.
Ball.jsx
import React, { PureComponent, Component } from 'react';
// Same with PureComponent
const Ball = React.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>
)
});
// class Ball extends PureComponent {
// getBackgroundColor(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 background;
// }
// render() {
// const { number } = this.props;
// return (
// <>
// <div className="ball" style={{'backgroundColor': this.getBackgroundColor(number)}}>{number}</div>
// </>
// )
// }
// }
export default Ball;
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Lotto</title>
<style>
.ball {
display: inline-block;
border: 1px solid black;
border-radius: 20px;
width: 40px;
height: 40px;
line-height: 40px;
font-size: 20px;
text-align: center;
margin-right: 20px;
}
</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>
client.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { hot } from 'react-hot-loader/root';
import Lotto from './Lotto'; // Class 사용
import LottoHook from './Lotto-hook'; // Hooks 사용
// const Hot = hot(Lotto);
const Hot = hot(LottoHook);
ReactDOM.render(<Hot />, document.getElementById('root')); // Class 사용
그 다음 제일 중요한 hooks 입니다.
Lotto-hook.jsx
import React from 'react';
import Ball from './Ball';
const { useState, useRef, useEffect, useMemo, useCallback } = React;
const getWinNumbers = () => {
console.log('Get win numbers');
const candidates = Array(45).fill().map((v, i) => i + 1); // Make 1 ~ 45 Arrays
const shuffled = [];
while(candidates.length > 0) {
shuffled.push(candidates.splice(Math.floor(Math.random() * candidates.length), 1)[0]);
} // Splice candidates Array when candidates length is 0
const winNumbers = shuffled.slice(0, 6); // Slice 6
const bonus = shuffled[shuffled.length - 1]; // Slice last element
return [...winNumbers, bonus]; // Combine two element
}
const LottoHook = () => {
// useMemo is remind variable when changed second paramter
// But this is [], so call once
// If not around getWinNumbers by useMemo, getWinNumbers is call when every rerendering
const lottoNumbers = useMemo(() => getWinNumbers(), []);
const [winNumbers, setWinNumbers] = useState(lottoNumbers);
const [winBalls, setWinBalls] = useState([]);
const [bonus, setBonus] = useState(null);
const [redo, setRedo] = useState(false);
const timeouts = useRef([]);
useEffect(() => {
console.log('Use Effect by timeouts current');
setWinBallsTimers();
return () => {
console.log('Clear timeouts current');
timeouts.current.map((v) => {
clearTimeout(v);
});
}
}, [timeouts.current]);
// Call by componentDidMount, componentDidUpdate(When timeouts.current is changed), componentWillUnmount(return)
useEffect(() => {
console.log('Same with componentDidMount');
}, []) // Same with componentDidMount because second parameter is []
const setWinBallsTimers = () => {
winNumbers.forEach((item, index) => {
timeouts.current[index] = setTimeout(() => {
if (index === winNumbers.length - 1) {
setBonus(item);
setRedo(true);
} else {
setWinBalls((prevWinBalls) => {
return [...prevWinBalls, item]
});
}
}, (index + 1) * 500)
});
}
const setBonusNumber = () => {
return (
<>
{!!bonus ? <Ball number={bonus}/> : null}
{!!redo ? <button onClick={clickRedo}>Redo</button> : null}
</>
)
}
// useCallback is remind function when winNumbers revised
const clickRedo = useCallback(() => {
console.log('Click redo');
console.log('Win numbers', winNumbers);
setWinNumbers(getWinNumbers());
setWinBalls([]);
setBonus(null);
setRedo(false);
timeouts.current = [];
}, [winNumbers]);
return (
<>
<div>
WinBalls
</div>
{
winBalls.map((value) => {
return <Ball key={value} number={value}/>
})
}
<div>
Bonus Ball
</div>
{setBonusNumber()}
</>
);
}
export default LottoHook;
주의 깊게 보셔야 할 점이 useEffect, useMemo, useCallback 인데요. useEffect 는 지난 시간에 포스팅 했지만 다시 정리해 보겠습니다.
* useEffect: 안에 내용을 실행한다. 두 번째 인자가 바뀔 때까지, 즉 두 번째 인자가 빈 배열일 경우 componentDidMount 와 같은 역할을 한다. componentDidUpdate 에서는 안에 if 문을 사용하여 어떤 변수가 바뀔 때 만 특정 함수를 실행해 주도록 제어해주었다면, useEffect 는 변수를 기준으로 여러개를 만들어 줄 수 있다. 예를 들어 timeouts.current 가 바뀔 때 해야하는일, winNumbers 가 바뀔 때 해야하는일 등등 각각의 변수가 바뀔 때 해야하는 일을 정의할 때 useEffect를 사용한다. 따라서 여러개의 useEffect를 사용 할 수 있다.
* useMemo: 어떤 함수의 리턴 값을 기억한다. 두 번째 인자가 바뀔 때까지, 즉 두 번째 인자가 바뀔 때 까지 그 함수의 리턴값을 기억하고 다시 실행하지 않는다. Hooks는 State가 바뀔 때 마다 다시 실행하는 경향이 있는데 useMemo에 두 번째 인자로 빈 배열을 넣어주면 다시 불리지 않는다. 만약 두 번째 인자가 바뀌게 되면 다시 useMemo로 감싸져 있는 함수가 실행 된다.
* useCallback: 어떤 함수 자체를 기억한다. 두 번째 인자가 바뀔 때 까지, 즉 두 번째 인자가 바뀔 때 까지 함수 자체를 기억하기 때문에 위에 코드에서 만약 두 번째 인자를 빈 배열로 하게 되면 함수가 다시 생성되지 않아서 함수 내의 값 (예를 들어 로그에 winNumbers)가 같은 값으로 유지 된다. 따라서 이러한 경우는 어떤 함수를 props로 자식 component 에 넘겨 줄 때 감싸면 좋다. 이유는 만약 useCallback으로 감싸지 않으면 함수가 자꾸 생성되고 그 함수를 props로 받은 자식들은 계속해서 렌더링을 하기 때문에 좋지 못하기 때문이다.
이정도로 정리할 수 있을것 같네요.
그럼 마지막으로 폴더 구조만 보고 정리하겠습니다.
제 코드는 저의 깃허브에서 보실 수 있습니다.
'React JS' 카테고리의 다른 글
[React 틱 택 토] Redux - React 프로젝트 사용 (2) | 2019.07.18 |
---|---|
[React 틱 택 토] Redux - React 프로젝트 세팅 (0) | 2019.07.18 |
[React 가위, 바위, 보] 라이프 사이클 (0) | 2019.07.08 |
[React JS] 프로젝트 셋팅 및 webpack, hot loader적용 (0) | 2019.07.08 |
[React 반응속도 체크] 조건문 및 타이머 (0) | 2019.07.04 |