React #3 _ quiz 미니 프로젝트
퀴즈 미니 프로젝트를 만들면서
React의 여러가지 기능들을 되돌아보는데,
한 가지 계속 반복해서 나오는 건 State 즉 useState에서
여러가지의 useState를 만들어서 쓰는 것도 가능하지만 되도록이면
하나의 useState에서 여러 데이터를 파생해서 쓰는 것이 React에서 유리하다.
예를들어,
const [userAnswer, setUserAnswers] = useState([]);
이런 식으로 유저가 고른 정답들이 저장되는 useState를 만들었을때
현재 몇 번 문제 인지, 몇 번 인덱스에 있는지 확인하기 위해서
const [index, setIndex] = useState(0); 이런식으로 만들 수 도 있지만
미리 만들어둔 useAnswer에서 유저가 고른 정답 목록의 길이를 가져오면 인덱스를 알 수 있으니
const activeQuestionIndex = userAnswer.length; 이런 식으로 파생해서 쓸 수 있다.
+ 순서 셔플할때 a.sort(() => Math.random() - 0.5 ); 하면 알아서 잘 섞인다...
무한루프를 방지하기 위한 useEffect 사용 예시(quiz 프로젝트에서) :
export default fanction QuestionTimer({timeout, onTimeout}) {
const [remainingTime, setRemainingTime] = useState(timeout);
setInterval(() => {
setRemainingTime(prevRemainingTime => prevRemainingTime = 100);
}, 100);
return <progress />
}
위 코드처럼 setInterval이 실행되면서 useState의 값을 새로 갱신하면 리액트는 state가 갱실될때마다
전체를 리로드 하게되는데 그러면 아래의 함수들이 다시 시작되면서 무한 setInterval이 실행된다.
그러므로 무한한 실행을 방지하기 위해서 useEffect를 사용해야한다.
그렇다면 useCallback은 왜 사용할까??
기본적으로 리액트 자바스크립트가 새롭게 랜더링 할때마다 보기에는 같은 값을 내보내는 거 같지만 계속해서
새로운 저장공간을 창출하여 새로운 값을 재 생성하여 내보내는 거다.
그래서 만약 return에 함수가 있다면 예를 들어
<QuestionTimer
timeout = {10000}
onTimeout = { () => handleSelectAnswer(null) } />
이런식으로 return에 함수가 있다면 이 함수는 랜더링 될때마다 계속해서 재 생성되고 새로운 함수를 만들어 내는 거라서
이것에 영향을 받는 <QuestionTimer /> 의 onTimeout은 계속해서 변화를 느끼고 실행하게 되는거라서 그 안에 존재하는
useEffect에서 의존성을 걸어뒀던 setTimeout이 계속 실행되는 거다.
그러므로 useCallback을 사용해서 함수 재생성을 방지해야한다.
그래서 아래와 같이 useCallback을 설정해줬다
const handleSelectAnswer = useCallback(
function handleSelectAnswer(selectedAnswer) {
setAnswerState('answered');
setUserAnswers((prevAnswer) => {
return [...prevAnswer, selectedAnswer];
});
setTimeout(() => {
if (selectedAnswer === QUESTIONS[activeQuestionIndex].answers[0]) {
setAnswerState('correct');
} else {
setAnswerState('wrong');
}
setTimeout(() => {
setAnswerState('');
}, 2000);
}, 1000);
},
[activeQuestionIndex]
);
const handleSkipAnswer = useCallback(() => handleSelectAnswer(null), [handleSelectAnswer]);
useCallback 또한 의존성(dependency)을 설정할 수 있어서 handleSkipAnswer에 영향을 주는 selectAnswer에 의존성을
부여해줬다.
여기서 살짝 멍청한 의문이 생긴다. 그러면 그냥 useEffect를 또 쓰면 되는 거 아닌가?
근데 아예 기능이 다르다.
useCallback은 함수 객체의 메모이제이션을 위해 사용되고. 반면, useEffect는 렌더링 후 부수 효과 처리를 위한 훅이다.
이 코드에서는 사용자의 선택을 처리하는 이벤트 핸들러를 효율적으로 관리하고, 불필요한 함수 재생성을 방지하기 위해 useCallback을 사용한 것이다. 따라서 useEffect는 이 경우 적합하지 않다.