Frontend Study

React #3 _ quiz 미니 프로젝트

jimmmy_jin 2024. 12. 28. 18:48

퀴즈 미니 프로젝트를 만들면서

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는 이 경우 적합하지 않다.