본문 바로가기
코딩 정보/React

[React] useEffect와 관련해 더 개념을 알아보자

by 꽁이꽁설꽁돌 2024. 7. 23.
728x90
반응형

목차

     

    내가 프로젝트를 진행하면서 알게 된 것들에 대해 정리 해보고자 한다.

     

    key와 index에 대해 더 알아보자

    아래 코드를 보면 Question 컴포넌트에 key값과 인덱스 값을 전달해 주고 있다.

     

    1. key대신 index를 넘기기

    key는 리액트의 기본 속성이라 오류에 직면하기 때문에 프롭스를 통해 전달해 값을 쓰려면 index를 통해 전달해 쓰자 

     

     

    2. key의 역할: 리랜더링

    key를 설정했는데 그 이유는 리랜더링이 일어나기 위해서는 값이 변화해야하는데 키를 통해 변화를 주어 리랜더링을 일으키고자 했기 때문이다.

     

    3.key는 여러 컴포넌트에서 사용 금지

    key는 unique한 값이기 때문에 여러 컴포넌트에서 동일한 키를 쓰게되면 예상치 못한 오류에 직면한다.

     

    import { useState, useCallback, useRef } from "react";
    import { questions } from "../question";
    import quizCompleteImg from "../assets/quiz-complete.png";
    import Question from "./Qusetion";
    import QuizIsComplete from "./QuizIsComplete";
    
    function Quiz() {
      //const [activeQuestionIndex, setActiveQuestionIndex] = useState(0);
    
      const [userAnswers, setUserAnswers] = useState([]);
      //사용자 답으로 파생 상태는 최대한 줄이는게 좋음
    
      const activeQuestionIndex = userAnswers.length;
    
      const quizIsComplete = userAnswers.length === questions.length;
      //배열이면 return []
      //객체이면 return {}
      //컴포넌트면 return ()
      //return 생략 가능
      const handleSelectAnswer = useCallback(function handleSelectAnswer(answer) {
        setUserAnswers((prev) => [...prev, answer]);
      }, []);
      //속성과 상태에 의존
      const handleSkipAnswer = useCallback(
        () => handleSelectAnswer(null),
        [handleSelectAnswer]
      );
    
      if (quizIsComplete) {
        return <QuizIsComplete userAnswers={userAnswers}></QuizIsComplete>;
      }
    
      return (
        <div id="quiz">
          <Question
            key={activeQuestionIndex}
            index={activeQuestionIndex}
            onSelectAnswer={handleSelectAnswer}
            handleSkipAnswer={handleSkipAnswer}
          ></Question>
        </div>
      );
    }
    export default Quiz;

     

    useEffect 사용 시 클린업 함수

    useEffect(() => {
        (이펙트 함수)
        return {
            (클린업 함수)
        };
    }, [의존값]);

     

    -클린업 함수-

    컴포넌트가 DOM에서 제거되는 시점 즉 컴포넌트가 더 이상 필요하지 않아서 화면에서 제거될 때,다른 페이지로 이동할 때 실행되는 함수이다.

     

     

    -주로 사용되는 예-

    • 타이머, 인터벌, 또는 디바운스와 같은 주기적인 작업을 해제시
    • 이벤트 리스너 등록 해제시
    • 기타 메모리 누수를 방지하기 위해 수행해야 하는 정리 작업을 할 시

     

    대표적인 예시

    import { useEffect, useState } from "react";
    
    function QuestionTimer({ timeout, onTimeout, mode }) {
      const [remainingTIme, setRemainingTime] = useState(timeout);
      //객체 생성으로 인해 계속 갱신
      useEffect(() => {
        //console.log("Setting Timeout");
        const Timer = setTimeout(onTimeout, timeout);
        return () => {
          clearTimeout(Timer);
        };
      }, [timeout, onTimeout]);
      useEffect(() => {
        //console.log("Setting Interval");
        const Timer = setInterval(() => {
          setRemainingTime((prevTime) => prevTime - 100);
        }, 100);
        return () => {
          clearTimeout(Timer);
        };
      }, [onTimeout]);
    
      return (
        <progress
          id="question-time"
          value={remainingTIme}
          max={timeout}
          className={mode}
        ></progress>
      );
    }
    
    export default QuestionTimer;

     

    비동기적 작동의 분석

    아래와 같은 컴포넌트안에 비동기적 함수가 있다면 작동 순서가 어떻게 될지 분석해 보았다.

    function Question({ index, onSelectAnswer, handleSkipAnswer }) {
      const [answer, setAnswer] = useState({
        selectedAnswer: "",
        isCorrect: null,
      });
    
      let timer = 10000;
    
      function handleSelectAnswer(answer) {
        setAnswer({
          selectedAnswer: answer,
          isCorrect: null,
        });
        setTimeout(() => {
          setAnswer({
            selectedAnswer: answer,
            isCorrect: questions[index].answers[0] === answer,
          });
          console.log("1a");
          setTimeout(() => {
            onSelectAnswer(answer);
            console.log("2a");
          }, 2000);
        }, 1000);
      }
      let answerState = "";
      if (answer.selectedAnswer) {
        timer = 1000;
        console.log("1b");
      }
      if (answer.isCorrect !== null) {
        timer = 2000; 
        console.log("2b");
      }
    
      return (
        <div id="question">
    
     
          <Answers
            answers={questions[index].answers}
            selectedAnswer={answer.selectedAnswer}
            answerState={answerState}
            handleSelectAnswer={handleSelectAnswer}
          ></Answers>
        </div>
      );
    }
    export default Question;

     

    1b -> 1a -> 1b -> 2b -> 2a 이런식으로 된다.

    그 이유는 다음과 같다.

     

    1. 첫번째 setTimeout을 만나게 된다. 1000초의 딜레이가 생긴 후 작동 되므로 넘어간다.

      setTimeout(() => {
          setAnswer({
            selectedAnswer: answer,
            isCorrect: questions[index].answers[0] === answer,
          });
          console.log("1a");
          setTimeout(() => {
            onSelectAnswer(answer);
            console.log("2a");
          }, 2000);
        }, 1000);

     

    2. selectedAnswer이 갱신 되었기 때문에 1b를 출력한다.

     if (answer.selectedAnswer) {
        timer = 1000;
        console.log("1b");
      }

     

    3. isCorrect는 갱신이 안되었기 때문에 2b는 넘어간다.

      if (answer.isCorrect !== null) {
        timer = 2000; 
        console.log("2b");
      }

     

    4. 1초가 지났으므로 1a 출력한다.

    5. 두번째 setTimeout을 만나게 된다. 2000초의 딜레이가 생긴 후 작동 되므로 넘어간다.

      setTimeout(() => {
          setAnswer({
            selectedAnswer: answer,
            isCorrect: questions[index].answers[0] === answer,
          });
          console.log("1a");
          setTimeout(() => {
            onSelectAnswer(answer);
            console.log("2a");
          }, 2000);
        }, 1000);

     

    6. selectedAnswer이 갱신 되었기 때문에 1b를 출력한다.

    if (answer.selectedAnswer) {
        timer = 1000;
        console.log("1b");
      }

     

    7. isCorrect가 갱신 되었기 때문에 2b를 출력한다.

      if (answer.isCorrect !== null) {
        timer = 2000; 
        console.log("2b");
      }

     

    8. 2000초의 딜레이가 생긴 후 작동 되어 2a를 출력한다.

      setTimeout(() => {
          setAnswer({
            selectedAnswer: answer,
            isCorrect: questions[index].answers[0] === answer,
          });
          console.log("1a");
          setTimeout(() => {
            onSelectAnswer(answer);
            console.log("2a");
          }, 2000);
        }, 1000);

     

     

    반응형