강의를 통해 알게된 것
useRef의 용법 중 DOM 요소 조작이 아닌, 변수 컨테이너로 사용하는 용법에 대해 알아보았습니다. 이걸 알았다면 부트캠프에서 진행한 팀 프로젝트가 한층 더 완성도가 높았을텐대 많이 아쉽네요. 어찌 보면 프론트엔드 공부를 아직 많이~ 못했기에 그럴수도 있겠죠. 매일 매일 부족함을 많이 느낍니다.
React.memo를 사용해서 props가 변하지 않을 때 리렌더링 되지 않게 시도해보았지만 잘 되지 않았다. 내가 모르는 부분이 더 있나보다. 일단 진도를 쭉쭉 나가서 한 사이클 돌린 뒤에 생각해봐야 할 문제인것 같다. 프로페셔널 프론트엔드 개발자가 되고자 하는 사람이 아직 리액트 1회독도 못했다니 😭
얼른! 한사이클 돌리고 오겠습니다. 고지가 얼마 안남았네요. 화이팅!
처음에 내가 짠 코드
import React, { useState } from 'react';
function ReactionCheck() {
const [bgColor, setBgColor] = useState('blue');
const [isPlaying, setIsPlaying] = useState(false);
const [startTime, setStartTime] = useState();
const [result, setResult] = useState();
const startGame = () => {
setIsPlaying(true);
setTimeout(() => {
setBgColor('aqua');
setStartTime(new Date().getTime());
}, 1000);
};
const onClickBox = () => {
let endTime = new Date().getTime();
if (isPlaying === false) return;
setBgColor('black');
setIsPlaying(false);
setResult(endTime - startTime + 'ms');
};
return (
<>
<h1>리액션 체크!</h1>
<button onClick={startGame}>시작버튼!</button>
<h2>아쿠아색이 되면 눌러주세요!</h2>
<div onClick={onClickBox} className={`gameBox ${bgColor}`}></div>
<h3>반응 속도 : {result}</h3>
</>
);
}
export default ReactionCheck;
강의 코드를 참고해서 개선한 코드
import React, { useState } from 'react';
function ReactionCheck() {
const [state, setState] = useState('none');
const [startTime, setStartTime] = useState();
const [results, setResults] = useState([]);
const [timeTrigger, setTimeTrigger] = useState();
const startGame = () => {
setState('waiting');
const timeout = setTimeout(() => {
setState('clickable');
setStartTime(new Date().getTime());
}, Math.floor(Math.random() * 1000) + 1000);
setTimeTrigger(timeout);
};
const onClickBox = () => {
if (state === 'none') return;
if (state === 'waiting') {
clearTimeout(timeTrigger);
setState('none');
return;
}
saveResult();
};
const saveResult = () => {
let endTime = new Date().getTime();
setState('none');
let result = endTime - startTime;
setResults(prev => [...prev, result]);
};
const renderAverage = () => {
return (
results.length !== 0 && (
<h3>평균 반응 속도 : {results.reduce((acc, cur) => acc + cur, 0) / results.length || 0} ms</h3>
)
);
};
return (
<>
<h1>리액션 체크!</h1>
<button onClick={startGame}>시작버튼!</button>
<h2>아쿠아색이 되면 눌러주세요!</h2>
<div onClick={onClickBox} className={`gameBox ${state}`}></div>
<>{renderAverage()}</>
</>
);
}
export default ReactionCheck;
변경점 1) state라는 state를 둬서, ‘none’, ‘waiting’, ‘clickable’이라는 상태로 나누어주었다. 이 상태에 따라 bgColor도 변하도록 했다.
변경점 2) renderAverage라는 함수를 선언해서 jsx안에서 조건문을 통해 render가 나뉘는걸 빼 보았다. 개인적으로 이게 더 보기 안좋은것 같은데… ? 차라리 jsx를 return 하기 보다는 반응 속도 계산 식만 빼는게 나아보인다. 어쨌든 이렇게 부분적인 렌더함수를 만들어 줄 수 있다는 점을 배웠다.
변경점 3) startGame 함수에서 무조건 1000ms 뒤에 클릭 하는게 아니라 랜덤값을 줘서 긴장감을 높여주었다.🤣
변경점 4) ‘waiting’ 상태일 때 반칙으로 먼저 눌러 버리면 clearTimeout을 사용해서 timeout을 없애줬다. 근데 class형 컴포넌트에서는 state이외의 변수를 저장해두면 rerendering되더라도 render 바깥에서 선언해두면 변수로 쓸 수 있는데 함수 컴포넌트는 변수로 사용하면 리렌더될때 초기화 되버려서 사용을 못하더라. 그래서 어쩔 수 없이 timeout을 state로 지정해둬서 clear할 때 사용해줬다.
⁉️ 함수 컴포넌트 일 때 state 이외에 변수 사용하는 방법이 없을까? 분명히 있어야 정상이다. 이게 안된다면 함수 컴포넌트의 매력이 엄청나게 떨어지게 된다. (단순 변수로 사용하고 싶어도 state로 선언해야하니 불필요한 rerender가 많아지니까)
💁♂️ 자 ~ 함수 컴포넌트에서 변수 사용법 여기 있습니다. 리액트 초보 티가 너무 많이 났네요~ **12. useRef 로 컴포넌트 안의 변수 만들기 그러고 보니 타입스크립트 할 때 useRef안의 값의 타입이 DOM일 때도 있었지만 container역할을 할 때! 도 있었어요 그땐 자세히 몰랐는데 지금 보니까 그말이 이 말이었네요 😅
const [state, setState] = useState('none');
const [results, setResults] = useState([]);
const timeout = useRef();
const startTime = useRef();
const startGame = () => {
setState('waiting');
timeout.current = setTimeout(() => {
setState('clickable');
startTime.current = new Date().getTime();
}, Math.floor(Math.random() * 1000) + 1000);
};
const onClickBox = () => {
if (state === 'none') return;
if (state === 'waiting') {
clearTimeout(timeout.current);
setState('none');
return;
}
saveResult();
};
const saveResult = () => {
let endTime = new Date().getTime();
setState('none');
let result = endTime - startTime.current;
setResults(prev => [...prev, result]);
};
✅ 와우! 대박! ㅎㅎ 이걸 몰라가지고 여태껏 엄청난 리렌더링을 유발시켰었네요!
부트캠프에서 팀 프로젝트 할 때 이걸 알았더라면 scroll 위치를 저장할 때 state가 아니라 useRef에 저장했으면 리렌더링을 훨씬 줄일 수 있었는데 바보짓이었네요 🤣
역시 사람은 배워야합니다. 너무 뿌듯하네요~!! 제로초 님 강의 너무 좋네요.
성능 체크
return (
<>
<h1>리액션 체크!</h1>
<button onClick={startGame}>시작버튼!</button>
<h2>아쿠아색이 되면 눌러주세요!</h2>
<div onClick={onClickBox} className={`gameBox ${state}`}></div>
// ---------------------------------------------------------
{results.length !== 0 && (
<>
<h3>평균 반응 속도 : {getResultsAvg()} ms</h3>
<button onClick={reset}>리셋</button>
</>
)}
</>
);
class에선 렌더 함수 안에, function에선 return문 안에 줄 그은 곳 위 아래에 리셋이 되었을 때 굳이 윗부분이 리렌더 되지 않아도 된다. 이게 싫다면 윗 부분을 컴포넌트로 분리해서 React.memo()로 래핑해주면 불필요한 리렌더링을 막아줄 수 있다.
import React, { useRef, useState } from 'react';
function ReactionCheck() {
const [state, setState] = useState('none');
const [results, setResults] = useState([]);
const timeout = useRef();
const startTime = useRef();
const startGame = () => {
setState('waiting');
timeout.current = setTimeout(() => {
setState('clickable');
startTime.current = new Date().getTime();
}, Math.floor(Math.random() * 1000) + 1000);
};
//...
const reset = () => {
setResults([]);
clearTimeout(timeout.current);
setState('none');
};
return (
<>
<ReactionHeader startGame={startGame}></ReactionHeader>
{/* <ReactionHeader startGame={startGame}></ReactionHeader> */}
{/* <h1>리액션 체크!</h1>
<button onClick={startGame}>시작버튼!</button>
<h2>아쿠아색이 되면 눌러주세요!</h2> */}
<div onClick={onClickBox} className={`gameBox ${state}`}></div>
{results.length !== 0 && (
<>
<h3>평균 반응 속도 : {getResultsAvg()} ms</h3>
<button onClick={reset}>리셋</button>
</>
)}
</>
);
}
export default ReactionCheck;
const ReactionHeader = React.memo(function ReactionHeader({ startGame }) {
return (
<>
<h1>리액션 체크!</h1>
<button onClick={startGame}>시작버튼!</button>
<h2>아쿠아색이 되면 눌러주세요!</h2>
</>
);
});
React.memo를 사용해서 props가 변하지 않으면 리렌더링이 되지 않도록 하였다. 하지만 데브툴에서 확인 해본 결과 리셋버튼을 눌렀을 때 ReactionHeader부분이 리렌더링 되는걸로 확인되었다.
⁉️ 왜그럴까? 왜 React.memo가 소용없을까?
아마도 startGame 내부의 값이 바뀌어서가 아닐까?
흠 .. 이것저것 찾아봐도 못찾았다. 혹시나 내용이 나올까? 해서 챕터4의 강의를 다 들어봤지만 나오지 않았다… 구글링 결과 벨로퍼트 리액트에서 useCallback, useMemo 등이 나오면서 설명을 해주시던데, 처음부터 따라간게 아니라 그런가 이해가 안되더라. 일단은 모르는채로 넘어가야겠다. 다음에 React.memo가 나오면 꼭 다시 해봐야지.
'FrontEnd > React.js' 카테고리의 다른 글
[React.js] useContext 찍먹, useFetch 구현, loading, error 상태 관리 (0) | 2022.10.31 |
---|---|
[React.js] axios instance.interceptor를 이용한 미들처리 (0) | 2022.10.29 |
[React.js] 리팩토링 하며 생긴 질문 - axios 의 catch() 와 try-catch의 catch가 같은건가? (0) | 2022.10.28 |
[React.js] 제로초 웹게임 - 5. 가위바위보 (life Cycle, useEffect) (0) | 2022.10.25 |
[React.js] 제로초 웹게임 - 3. 숫자야구 (shouldComponentUpdate, React.memo) (0) | 2022.10.24 |
[React.js] 제로초 웹게임 - 2. 끝말잇기 (Webpack, Babel) (0) | 2022.10.22 |
[React.js] 제로초 웹게임 - 1. 구구단 (react, babel) (0) | 2022.10.21 |
[React.js] to-do-app, fetch => Axios 마이그레이션 과정 (0) | 2022.10.20 |
댓글