일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 계산맞추기 게임
- You are importing createRoot from "react-dom" which is not supported. You should instead import it from "react-dom/client"
- Concurrently
- 인프런
- 노드에 리액트 추가하기
- props
- intellij
- ReactDOM.render is no longer supported in React 18. Use createRoot instead
- 모던자바스크립트
- Do it 자바스크립트 + 제이쿼리 입문
- spring-boot
- 모두의 파이썬
- intllij 내 Bean을 찾지 못해서 발생하는 오류
- react
- 거북이 대포 게임
- JS 개념
- googleColaboratory
- node.js로 로그인하기
- vs code 내 node
- node.js 설치
- Colaboratory 글자 깨짐
- 리액트
- 타자 게임 만들기
- Python
- react오류
- 따라하며 배우는 노드 리액트 기본 강의
- 웹 게임을 만들며 배우는 리액트
- 자바스크립트
- DB Browser
- Spring-Framework
- Today
- Total
프로그래밍 삽질 중
[웹 게임을 만들며 배우는 React] - 로또추첨기, useEffect, useCallback 본문
출처: https://www.inflearn.com/course/web-game-react/dashboard
* HOC (하이오더컴포넌트) = 고차 컴포넌트
▶ 컴포넌트 로직을 재사용하기 위해 사용되고 컴포넌트를 가져와 새 컴포넌트를 반환
▶ 컴포넌트를 인자로 받거나 반환하는 함수
▶ 컴포넌트를 감싸는 것 (예시에서는 memo 의미))
import React, { memo } from "react";
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;
* 리액트 장점
- 기존의 javascript의 경우 document.querySelector나 jqeury 경우 직접 입력해 DOM을 건듦
- 반면 리액트는 state만 변경하고 reqct가 저절로 화면을 그려줌
* Hooks
- 순서가 바뀌면 안 됨
- 조건문 안에 절대 넣으면 안 되고 함수나 반복문 안에도 웬만하면 넣으면 안 됨
- uesState는 항상 최상위에 위치
* componentDidUpdate(class 컴포넌트에서만 사용)
▶ 갱신이 일어난 직후에 호출됨, 최초 렌더링에서는 호출되지 않음
▶ 조건문으로 감싸지 않으면 무한반복이 될 수 있음
출처 : https://ko.reactjs.org/docs/react-component.html
//componentDidUpdate(prevProps, prevState, snapshot)
//예시
componentDidUpdate(prevProps) {
// 전형적인 사용 사례 (props 비교를 잊지 마세요)
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
[Ball.jsx]
import React, { memo } from "react";
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;
[LottoClass.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 LottoClass extends Component {
state = {
winNumbers: getWinNumbers(), // 당첨 숫자들
winBalls: [],
bonus: null, // 보너스 공
redo: false,
};
timeouts = [];
runtimeouts = () => {
console.log("runtimeouts");
const { winNumbers } = this.state;
for (let i = 0; i < winNumbers.length - 1; i++) {
this.timeouts[i] = setTimeout(() => {
this.setState((prevState) => {
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
});
}, (i + 1) * 1000);
}
this.timeouts[6] = setTimeout(() => {
this.setState({
bonus: winNumbers[6],
redo: true,
});
}, 7000);
};
componentDidMount() {
console.log("didMount");
this.runtimeouts();
}
// onClickRedo 누를 때 setTimeout 다시 실행해야 함
componentDidUpdate(prevProps, prevState) {
console.log("didUpdate");
//이 조건문이 중요(redo를 눌렀을때만, 조건문이 없을 경우 매 순간 runTimouts이 실행됨)
if (this.state.winBalls.length === 0) {
this.runtimeouts();
}
}
//setTimeout, setInterval 정리(메모리 누수 발생 방지)
componentWillUnmount() {
this.timeouts.forEach((v) => {
clearTimeout(v);
});
}
onClickRedo = () => {
console.log("onClickRedo");
this.setState({
winNumbers: getWinNumbers(), // 당첨 숫자들
winBalls: [],
bonus: null, // 보너스 공
redo: false,
});
this.timeouts = [];
};
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 LottoClass;
* useEffect
▶ input 요소가 빈배열이면 componentDidMount와 동일
▶ 배열의 요소가 있으면 componentDidMount, componentDidUpdate 둘 다 수행
▶ useRef 사용 시 current와 함께 쓸 것
▶ componentWillUnmount는 useEffect의 return
출처 : https://ko.reactjs.org/docs/hooks-reference.html#useeffect
//기본값
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
class에서 hooks에서 useEffect 사용할 경우
클래스의 경우
//class
runtimeouts = () => {
console.log("runtimeouts");
const { winNumbers } = this.state;
for (let i = 0; i < winNumbers.length - 1; i++) {
this.timeouts[i] = setTimeout(() => {
this.setState((prevState) => {
return {
winBalls: [...prevState.winBalls, winNumbers[i]],
};
});
}, (i + 1) * 1000);
}
this.timeouts[6] = setTimeout(() => {
this.setState({
bonus: winNumbers[6],
redo: true,
});
}, 7000);
};
componentDidMount() {
console.log("didMount");
this.runtimeouts();
}
// onClickRedo 누를 때 setTimeout 다시 실행해야 함
componentDidUpdate(prevProps, prevState) {
console.log("didUpdate");
//이 조건문이 중요(redo를 눌렀을때만, 조건문이 없을 경우 매 순간 runTimouts이 실행됨)
if (this.state.winBalls.length === 0) {
this.runtimeouts();
}
}
//setTimeout, setInterval 정리(메모리 누수 발생 방지)
componentWillUnmount() {
this.timeouts.forEach((v) => {
clearTimeout(v);
});
}
onClickRedo = () => {
console.log("onClickRedo");
this.setState({
winNumbers: getWinNumbers(), // 당첨 숫자들
winBalls: [],
bonus: null, // 보너스 공
redo: false,
});
this.timeouts = [];
};
▶ 두 번째 배열에 들어갈 부분, 즉 위의 예시에서 runtimeouts, 아래의 예시에서는 useEffect가 실행될 두 번째 인자([])
즉, componentDidMount, componentDidUpdate 둘 다 수행하는 조건 = timeouts.current가 빈 배열일 때
useEffect(() => {
console.log("useEffect");
for (let i = 0; i < winNumbers.length - 1; i++) {
timeouts.current[i] = setTimeout(() => {
setWinBalls((prevBalls) => [...prevBalls, winNumbers[i]]);
}, (i + 1) * 1000);
}
timeouts.current[6] = setTimeout(() => {
setBonus(winNumbers[6]);
setRedo(true);
}, 7000);
return () => {
timeouts.current.forEach((v) => {
clearTimeout(v);
}); //componentWillUnmount
};
}, [timeouts.current]);
const onClickRedo = () => {
console.log("onClickRedo");
setWinNumbers(getWinNumbers());
setWinBalls([]);
setBonus(null);
setRedo(false);
timeouts.current = [];
};
* Hooks 시 문제점
- getWinNumbers 함수에 console.log("getWinNumbers") 추가 시 매번 실행
= 함수 컴포넌트가 매번 재실행됨
- 다시 실행되지 않고 기억하게 하는 것 = useMemo
* useRef
▶ 일반 값을 기억
* useMemo
▶ 복잡한 함수 결과값을 기억(리턴값)
▶ 두 번째 인자가 바뀌지 않는 한 getWinNumbers()는 다시 실행되지 않음
▶ 두 번째 요소 배열이 바뀌면 getWinNumbers()도 다시 실행됨
useMemo(() => getWinNumbers(), []);
* useCallback
▶ 함수 자체를 기억
▶ 두 번째 인자가 바뀌지 않는 한 useCallback은 다시 실행되지 않음
▶ 두 번째 요소 배열이 바뀌면 useCallback도 다시 실행됨
▶ useCallback 안에서 쓰이는 state는 input에다가도 넣어야 함
- 반드시 useCallback을 사용해야 할 때 = 자식 컴포넌트에서 함수를 넘길 때
▶ useCallback이 없으면 매번 새로운 함수가 실행됨(부모가 함수를 바꿀 때마다 자식 컴포넌트가 매번 리렌더링함)
▶useCallback이 있어야 부모로 받은 함수가 계속 같다고 자식 컴포넌트가 인식함
* Hooks
import React, {
useState,
useRef,
useEffect,
useMemo,
useCallback,
} 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];
}
const Lotto = () => {
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("useEffect");
for (let i = 0; i < winNumbers.length - 1; i++) {
timeouts.current[i] = setTimeout(() => {
setWinBalls((prevBalls) => [...prevBalls, winNumbers[i]]);
}, (i + 1) * 1000);
}
timeouts.current[6] = setTimeout(() => {
setBonus(winNumbers[6]);
setRedo(true);
}, 7000);
return () => {
timeouts.current.forEach((v) => {
clearTimeout(v);
}); //componentWillUnmount
};
}, [timeouts.current]);
const onClickRedo = useCallback(() => {
console.log("onClickRedo");
console.log(winNumbers);
setWinNumbers(getWinNumbers());
setWinBalls([]);
setBonus(null);
setRedo(false);
timeouts.current = [];
}, [winNumbers]);
return (
<>
<div>당첨 숫자</div>
<div id="결과창">
{winBalls.map((v) => (
<Ball key={v} number={v} />
))}
</div>
<div>보너스!</div>
{bonus && <Ball number={bonus} />}
{redo && <button onClick={onClickRedo}>한 번 더!</button>}
</>
);
};
export default Lotto;
* [useEffect] ajax를 componentDidMount만 실행하고 싶을 경우
useEffect(() => {
//ajax
}, []);
* [useEffect] ajax를 componentDidUpdate만 실행하고 componentDidMount에서 실행 X
const mounted = useRef(false);
useEffect(() => {
if(!mounted.current) {
mounted.current = true
} else {
//ajax
}
}, [바뀌는 값]) //componentDidUpdate만 실행하고 componentDidMount에서 실행x
'과거 프로그래밍 자료들 > React' 카테고리의 다른 글
[웹 게임을 만들며 배우는 React] - 지뢰찾기(1), contextAPI (0) | 2022.09.30 |
---|---|
[웹 게임을 만들며 배우는 React] - 틱택토, useReducer, useCallback (0) | 2022.09.29 |
[웹 게임을 만들며 배우는 React] - 가위바위보, 커스텀 훅 (0) | 2022.09.29 |
[웹 게임을 만들며 배우는 React] - 가위바위보, 라이프사이클 (0) | 2022.09.28 |
[웹 게임을 만들며 배우는 React] - 반응속도 체크, state와 ref 차이 (0) | 2022.09.28 |