Today I Learned ... react.js
🙋♂️ React.js Lecture
🙋 My Dev Blog
React Lecture CH 5
1 - 라이프사이클
2 - setInterval과 라이프사이클 연동
3 - 가위바위보 게임
4 - 고차함수 (Higher-order Func)
5 - useEffect
6 - 클래스 vs Hooks 라이프사이클 비교
❗️ 주의
- 클래스에서 Hooks로 변경할 때,
생명주기 함수(life cycle)는 Hooks에 존재하지 않음.
// Hooks
import React, { useState, useRef, useEffect } from 'react';
const rspCoords = {
바위: '0',
가위: '-142px',
보: '-284px',
};
const scores = {
가위: 1,
바위: 0,
보: -1,
};
const computerChoice = (imgCoord) => {
return Object.entries(rspCoords).find(function (v) {
return v[1] === imgCoord;
})[0];
};
const RSP = () => {
const [result, setResult] = useState('');
const [imgCoord, setImgCoord] = useState(rspCoords.바위);
const [score, setScore] = useState(0);
const interval = useRef();
// ✅ componentDidMount, componentDidUpdate의 역할
useEffect(() => {
interval.current = setInterval(changeHand, 100);
return () => {
// componentWillUnmount 역할
clearInterval(interval.current);
};
}, [imgCoord]); // useEffect를 실행하고 싶은 (바뀌는) state를 적어줌. (리액트가 지켜볼 state.)
// ❗️ 빈 배열이면 - 맨 처음 딱 한번만 실행됨.
const changeHand = () => {
if (imgCoord === rspCoords.바위) {
setImgCoord(rspCoords.가위);
} else if (imgCoord === rspCoords.가위) {
setImgCoord(rspCoords.보);
} else if (imgCoord === rspCoords.보) {
setImgCoord(rspCoords.바위);
}
};
const onClickBtn = (choice) => () => {
clearInterval(interval.current);
const myScore = scores[choice];
const cpuScore = scores[computerChoice(imgCoord)];
const diff = myScore - cpuScore;
if (diff === 0) {
setResult('비겼습니다.');
} else if ([-1, 2].includes(diff)) {
setResult('이겼습니다!');
setScore((prevScore) => prevScore + 1);
} else {
setResult('졌습니다!');
setScore((prevScore) => prevScore - 1);
}
setTimeout(() => {
interval.current = setInterval(changeHand, 100);
}, 1000);
};
return (
<>
<div
id="computer"
style={{
background: `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0`,
}}
/>
<div>
<button id="rock" className="btn" onClick={onClickBtn('바위')}>
바위
</button>
<button id="scissor" className="btn" onClick={onClickBtn('가위')}>
가위
</button>
<button id="paper" className="btn" onClick={onClickBtn('보')}>
보
</button>
</div>
<div>{result}</div>
<div>현재 {score}점</div>
</>
);
};
export default RSP;
const [result, setResult] = useState('');
useEffect
를 사용할 수 있다.
useEffect
는 componentDidMount + componentDidUpdate의 역할을 한다.- 단, 1:1로 대응하지는 않는다.
- useEffect의 첫번째 인수로는 콜백이 들어가고, 두번째 인수로는 deps(dependencies) 배열이 들어간다.
= useEffect의 콜백을 다시 실행할 state만 적기.- deps가 빈 배열이면 최초 1회만 실행되고,(componentDidMount)
- 안에 state를 적어주면, 해당 state를 지켜보게 된다.
(그 state가 업데이트되면 다시 실행됨)
- 콜백 내에
return() {}
으로 componentWillUnmount 역할을 할 수 있다.
+) 참고로, Hooks가 나오기 전에는 recompose 라이브러리의 lifecycle
메서드로 '함수형'에서도 생명주기 함수를 사용할 수 있었다.
React 공식 문서 살펴보기
React가 DOM을 업데이트한 뒤 추가로 코드를 실행해야 하는 경우가 있습니다.
네트워크 리퀘스트, DOM 수동 조작, 로깅 등은 정리(clean-up)가 필요 없는 경우들입니다.
이러한 예들은 실행 이후 신경 쓸 것이 없기 때문입니다.
class 컴포넌트에서 render 메서드 그 자체는 side effect를 발생시키지 않습니다.
이때는 아직 이른 시기로서 이러한 effect를 수행하는 것은 React가 DOM을 업데이트하고 난 이후입니다.
(componentDidMount, componentDidUpdate)
렌더링 이후에 어떤 일을 수행해야 하는지.
리액트는 우리가 넘긴 함수 (effect)를 기억했다가 DOM 업데이트 이후에 불러냄.
useEffect를 컴포넌트 내(함수형 컴포넌트)에 둠으로써
effect를 통해 state 변수에도 접근할 수 있게 됨.
-> 함수 범위 안에 존재하므로 값을 얻을 수 있음.
useEffect는 기본적으로 첫 렌더링 & 이후 모든 업데이트마다 매번 실행됨.
-> 즉, 렌더링 이후에 매번 발생하는 것.
1️⃣ class 사용시
componentDidMount에 구독 설정 후,
componentWillUnmount에서 정리.
구독(subscription)의 추가와 제거를 위한 코드는 결합도가 높기 때문에 useEffect는 이를 함께 다루도록 고안되었습니다.
effect가 함수를 반환하면 React는 그 함수를 정리가 필요한 때에 실행시킬 것입니다.
🙋♀️ effect에서 함수를 반환하는 이유는 무엇일까요?
- 이는 effect를 위한 추가적인 정리(clean-up) 메커니즘입니다. 모든 effect는 정리를 위한 함수를 반환할 수 있습니다.
🙋♀️ React가 effect를 정리(clean-up)하는 시점은 정확히 언제일까요?
- React는 컴포넌트가 마운트 해제되는 때에 정리(clean-up)를 실행합니다. 하지만 위의 예시에서 보았듯이 effect는 한번이 아니라 렌더링이 실행되는 때마다 실행됩니다. React가 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect 또한 정리하는 이유가 바로 이 때문입니다.
- useEffect가 컴포넌트의 렌더링 이후에 다양한 side effects를 표현할 수 있음.
- effect에 정리(clean-up)가 필요한 경우에는 함수를 반환