useEffect는 어떻게 작동할까

핫걸코더지망생·2024년 3월 18일
0

React

목록 보기
4/4
post-thumbnail

최근 useEffect의 호락호락하지 않음을 경험하고 좀 더 자세히 공부해야겠다 라는 생각이 들었습니다.
무슨일이었냐면 제가 작업 중이던 프로젝트에서 useEffect 의존성 배열인자가 계속 호출되어 무한루프가 발생했습니다.
그 결과로 파이어베이스에서 무료로 사용할 수 있도록 할당된 읽기 50000건을 하루만에 사용해 버렸습니다.🧔🏻‍♀️

=> useGetFeedData.tsx

import { getDoc, doc } from 'firebase/firestore';
import { appFireStore } from '@/firebase/config';
import useAuthContext from '@/hook/useAuthContext';  
import useAuthContext from '@/hook/useAuthContext';

export default function useGetFeedData() {
  const { user } = useAuthContext();

  const getFeedData = async (feedId: string) => {
    if (user === null) {
      return;
    }

    try {
      const docSnap = await getDoc(doc(appFireStore, 'feed', feedId));

      return docSnap.data();
    } catch (error) {
      console.log(error);
    }
  };

  return getFeedData;
}

문제 발생 이유

    1. 기존에 게시물을 그려주는 FeedInfo라는 컴포넌트에서 사용중인 useEffct의 의존성배열에 getFeedData를 추가하라는 경고문 발생 (경고문 발생하면 고민없이 수정하는 잘못된 습관의 쓴맛을 보았습니다..)
    1. 의존성배열에 feed.id와 getFeedData 추가
    1. FeedInfo에서 setFeedData를 통해서 feedData를 업데이트하고 있음 이 때 상태값이 업데이트 되면서 FeedInfo 컴포넌트가 리렌더링 됨
    1. useGetFeedData 훅스에서 재호출로 인한 이전 렌더링 됐을 당시 주소값 참조와 달라짐
    1. 의존성 배열 요소로 지정한 getFeedData함수의 참조값 변화 발생
    1. useEffect 다시 실행
    1. 무한 반복해서 하루에 5만개의 읽기 값이 찍힘 실제 출시한 서비스였으면 돈방석 앉았다고 기뻐했을 듯

해결방법

  • useCallback을 사용해서 한번만 호출하도록 useGetFeedData 훅 수정
    FeedInfo에서 의존성배열에 있는 getFeedData가 더이상 반복 호출하지 않음

이 과정을 겪으며 나 너무 useEffect라는 훅을 띄엄띄엄 보고 있었구나 공부해야 겠다는 마음을 많이 느꼈습니다.



1. 리액트의 생명주기

useEffct실행순서컴포넌트의 생명주기와 밀접한 관계가 있습니다. 그래서 먼저 리액트의 생명주기에 대해 이해하고 있는게 중요합니다.

📍 클래스 컴포넌트의 생명 주기 (Life Cycle)

컴포넌트의 생애주기는 크게 세 가지로 나눌 수 있습니다.
컴포넌트는 생성(mounting) -> 업데이트(updating) -> 제거(unmounting)


이미지 출처: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

(1) 마운트 : 처음 컴포넌트가 나타났을 때
처음 state와 props를 가지고 컴포넌트를 생성합니다.

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount

(2) 업데이트 : 컴포넌트 상태값이 바뀔 때
마운트 완료 후 상태값이나 prop의 변화가 생겼을 때 리액트가 감지하고 컴포넌트에 업데이트해줍니다.

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • componentWillUpdate
  • componentDidUpdate

(3) 언마운트 : 컴포넌트가 사라질 때
언마운트에서는 componentWillUnmount가 실행됩니다. 컴포넌트를 완전히 DOM에서 제거하는 시점
componentWillUnmount : 컴포넌트가 사라지기 바로 직전에 호출



2. useEffect의 실행순서

useEffect(setup, dependencies)
useEffect 의 실행 순서에 대해 간단한 예제로 살펴보겠습니다. (예제는 멋사5기 교안을 참고했습니다.)

📍useEffect가 실행 되는 시점

// 1.컴포넌트가 업데이트 될 때마다 매번 실행
useEffect(()=>{
	console.log('hello world');
})

// 2.처음에만 실행
useEffect(()=>{
	console.log('hello world');
}, [])

// 3.변수들의 변화가 일어날 때마다 실행
useEffect(()=>{
	console.log('hello world');
}, [변수1, 변수2...])

📍useEffect 뒷정리하기

  • 컴포넌트가 언마운트되기 전이나 업데이트 되기 직전에 어떤한 작업을 수행하고 싶다면 useEffect에서 뒷정리 함수를 반환해야한다. ( 뒷정리 함수 == cleanup 함수 )
  • cleanup함수가 호출되면 어떤 값을 을 보여줄까? => 업데이트 되기 직전의 값을 보여줌
  • 언마운트 될 때만 ! cleanup 함수를 호출하고 싶다? => useEffect의 두번째 파라미터에 빈배열을 넣어주라

📍코드로 보는 cleanup 함수

- 성능이슈 발생 코드

import React, { useState, useEffect } from 'react';

function Time(props) {
  const [today, setToday] = useState(new Date());
  const [hour, setHour] = useState(today.getHours());
  const [min, setMin] = useState(today.getMinutes());
  const [sec, setSec] = useState(today.getSeconds());
  console.log("렌더링이 됩니다..?")//렌더링이 잘 되는지 확인용! 꼭 넣고 진행해주세요.

  // 성능이슈가 발생되는 공간
  setInterval(() => {
     const t = new Date();
     setToday(t);
     setHour(t.getHours());
     setMin(t.getMinutes());
     setSec(t.getSeconds());
  }, 1000);

  return (
    <div>
      <h1>
        시간 : {hour}{min}{sec}</h1>
    </div>
  );
}

function App() {
  return (
    <div>
      <Time/>
    </div>
  );
}

export default App;


- 클린업 함수 사용 코드

import { useState, useEffect } from "react";

function Time(props) {
  const [today, setToday] = useState(new Date());
  const hour = today.getHours();
  const min = today.getMinutes();
  const sec = today.getSeconds();

	console.log("렌더링이 됩니다..?")
  useEffect(() => {
    let time = setInterval(() => {
      const t = new Date();
      setToday(t);
    }, 1000);
    return () => {
      clearInterval(time);
    };
  }, [today]);

  return (
    <div>
      <h1>
        시간 : {hour}{min}{sec}</h1>
    </div>
  );
}

export default Time;

Reference

profile
산은 산, 물은 물, 코드는 코드

0개의 댓글