Today I Learned ... react.js
🙋♂️ React.js Lecture
🙋 My Dev Blog
React Lecture CH 6
1 - 로또 추첨기 컴포넌트
2 - setTimeout 중복 사용
3 - componentDidUpdate
4 - useEffect - 업데이트 감지
5 - useMemo, useCallback
6 - Hooks Tips
Lotto.jsx
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(), // [...winNumbers, bonusNumber]
winBalls: [],
bonus: null,
redo: false,
};
onClickRedo = () => {};
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} />}
<button onClick={redo ? this.onClickRedo : () => {}}>한번 더!</button>
</>
);
}
}
export default Lotto;
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];
}
candidate
에 저장.오름차순 | 내림차순 |
---|---|
sort((a, b) => a - b) | sort((a, b) => a + b) |
-> 즉, getWinNumbers() = [1,3,6,8,11,22,7]
+) 하위 컴포넌트인 Ball을 분리하여 임포트해주었다.
Ball.jsx
import React, { memo } from 'react';
// state를 안쓸 때는 - 함수 컴포넌트로. (Hooks 아님)
const Ball = 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>
);
});
export default Ball;
// 하이어오더 컴포넌트 (memo) = 컴포넌트를 컴포넌트로 감쌈. pure component 역할을 함.
state를 사용하지 않아도 되는 하위 컴포넌트는 함수형 컴포넌트로 작성해준다.
함수형에서 PureComponent 역할을 하려면?
->memo()
로 감싸준다.
memo
- 하이어오더 컴포넌트.
(컴포넌트를 다른 컴포넌트로 감쌈)- 함수형 컴포넌트를 PureComponent로 만들기 위해서는 React.memo()를 컴포넌트에 감싸준다.
<style>태그
를 적용해보자.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>로또 추첨기</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>
<script src="./dist/app.js"></script>
</body>
</html>
-> div.ball을 동그랗게 만들어주고, border과 width*height를 정해줌.
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(), // [...winNumbers, bonusNumber]
winBalls: [],
bonus: null,
redo: false,
};
componentDidMount() {
for (let i = 0; i < this.state.winNumbers.length - 1; i++) {
// length - 1을 해서 마지막 보너스 공은 빼줌
setTimeout(() => {
this.setState((prevState) => {
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
});
}, (i + 1) * 1000);
}
}
onClickRedo = () => {};
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} />}
<button onClick={redo ? this.onClickRedo : () => {}}>한번 더!</button>
</>
);
}
}
export default Lotto;
componentDidMount
에 입력해준다.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(), // [...winNumbers, bonusNumber]
winBalls: [],
bonus: null,
redo: false,
};
timeouts = [];
runTimeouts = () => {
const { winNumbers } = this.state;
for (let i = 0; i < this.state.winNumbers.length - 1; i++) {
// length - 1을 해서 마지막 보너스 공은 빼줌
this.timeouts[i] = setTimeout(() => {
this.setState((prevState) => {
const { winNumbers } = this.state;
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
});
}, (i + 1) * 1000);
}
this.timeouts[6] = setTimeout(() => {
this.setState({
bonus: winNumbers[6],
redo: true,
});
}, 7000);
};
componentDidMount() {
this.runTimeouts();
}
componentWillUnmount() {
this.timeouts.forEach((v) => clearTimeout(v));
}
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;
timeouts
는 클래스의 프로퍼티로, state처럼 값이 바뀌면 리렌더링 될 필요가 없을 떄 사용함.
- 여기서는 clearTimeout을 해줘야하므로 (componentWillUnmount에서) 식별자가 필요해서 사용했음.
componentWillUnmount() {
this.timeouts.forEach((v) => clearTimeout(v));
}
timeouts
라는 배열에 setTimeout 함수 7개가 담겨있음.1초 간격으로 winNumbers와 마지막 bonusNumber이 나온다.
-> 하위 컴포넌트 <Ball />
로 props인 number
에 자신의 state winBalls
와 bonus
를 전달해줌.
마지막에 bonus까지 나오게 되면, '한번 더' 버튼이 렌더링 된다.
{redo && <button onClick={this.onClickRedo}>한번 더!</button>}
runTimeouts = () => {
const { winNumbers } = this.state;
for (let i = 0; i < this.state.winNumbers.length - 1; i++) {
// length - 1을 해서 마지막 보너스 공은 빼줌
this.timeouts[i] = setTimeout(() => {
this.setState((prevState) => {
const { winNumbers } = this.state;
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
});
}, (i + 1) * 1000);
}
this.timeouts[6] = setTimeout(() => {
this.setState({
bonus: winNumbers[6],
redo: true,
});
}, 7000);
};
- i=0 ~ 5까지 this.timeouts[i]에 setTimeout 저장
-> winBalls에 이전 winBalls + winNumbers 추가
(push 대신 ...으로 요소 추가)
- for loop 종료 후, this.timeouts[6]에 setTimeout 저장
-> bonus에 winNumbers[6] 추가 후, redo를 true로 설정
-> button 렌더링 되게 (JSX부분 단축평가 &&에 의해)
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(), // [...winNumbers, bonusNumber]
winBalls: [],
bonus: null,
redo: false,
};
timeouts = [];
runTimeouts = () => {
const { winNumbers } = this.state;
for (let i = 0; i < this.state.winNumbers.length - 1; i++) {
// length - 1을 해서 마지막 보너스 공은 빼줌
this.timeouts[i] = setTimeout(() => {
this.setState((prevState) => {
const { winNumbers } = this.state;
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
});
}, (i + 1) * 1000);
}
this.timeouts[6] = setTimeout(() => {
this.setState({
bonus: winNumbers[6],
redo: true,
});
}, 7000);
};
componentDidMount() {
this.runTimeouts();
}
componentWillUnmount() {
this.timeouts.forEach((v) => clearTimeout(v));
}
onClickRedo = () => {
this.setState({
winNumbers: getWinNumbers(), // [...winNumbers, bonusNumber]
winBalls: [],
bonus: null,
redo: false,
});
this.runTimeouts();
};
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;
onClickRedo = () => {
this.setState({
winNumbers: getWinNumbers(), // [...winNumbers, bonusNumber]
winBalls: [],
bonus: null,
redo: false,
});
this.runTimeouts();
};
onClickRedo
- setState로 모든 state를 초기값으로 다시 리셋함
- this.runTimeouts() 실행
-> setTimeout() 총 7개 실행 (위에 코드 참조)