문제 사항

졸업작품을 제작하는 과정에서 컴포넌트 폴더 따로, 페이지 폴더 따로, axios 요청하는 함수 폴더 따로 << 이런 식으로 파일 구조를 생성하여 코드를 짜고 있었다. 근데 문제는 컴포넌트에서 useEffect를 활용하여 axios로 데이터를 요청하는 함수를 call 했을 때, 무수히 많은 데이터 요청을 보내는 것을 확인할 수 있었다. 그래서 의존성 배열에 아무것도 넣지 않고 대괄호만 띡 넣었더니 React놈이 싫다고 eslint error를 보내버린다.

image.png

dependency에다가 함수(axios 요청) 좀 추가하란다. 보기 싫어서, 얘 말 따랐더니 의존성 배열이 없는 코드와 마찬가지로 무수히 많은 요청을 보내버린다. 이를 해결해보자.

원인

의존성 배열에 추가한 함수가 컴포넌트 리렌더링할 때마다 새롭게 생성되기 때문이다.

‘어? 난 리렌더링 한 적 없는데? 왜 지 멋대로지’

실제로 내가 생각했던 생각이다. 알고나니 이게 정말 무지하다는 생각이 들었다(공부 더 해야겠다..). 간략하게 말하자면 첫 줄이고, 디테일하게 파보자. 우선 기존 코드를 보자.

//loadSuggestData.js
import axios from "axios";
import { useState } from "react"

const useLoadSuggestData = () => {
  const [데이터, set데이터] = useState([]);

  const loadSuggestData = async () => {
    try {
      const response = await axios.get('api 요청 url')
      set데이터(response);
      console.log("추천 데이터 받아오기 성공")
    } catch (error) {
      console.error(error);
    }
  }
  return { suggestData, loadSuggestData };
}

export default useLoadSuggestData;
//컴포넌트
const suggestComponent = () => {
  const { suggestData, loadSuggestData } = useLoadSuggestData();

  useEffect(() => {
    loadSuggestData();
  }, [loadSuggestData]);

  return (
	  어쩌구 저쩌구
  )
}
  1. 함수는 참조가 계속 변경됨

    useLoadSuggestData에서 반환된 loadSuggestData 는 함수다. 일반적으로 함수는 참조형 데이터이기에, 컴포넌트가 리렌더링될 때마다 새로운 함수 객체가 생성된다. React놈은 이걸 새로운 참조로 인식해 loadSuggestData가 변경된 것으로 판단한다. 그러므로 useEffect가 다시 실행되는 것이다.

  2. 무한 루프 발생 과정

    1. 컴포넌트가 처음 렌더링될 때, loadSuggestData가 생성되고 useEffect가 실행되어 데이터를 요청한다.
    2. HTTP 요청이 완료되면 다시 리렌더링 한다.
    3. 컴포넌트가 리렌더링 되면서 loadSuggestData 가 다시 생성된다.
    4. useEffect 는 “미친거 또 바꼈네”하면서 다시 실행된다.

    이 루프로 무한 데이터 요청을 보내는 것이다.

  3. 왜 리렌더링이 발생하나?

    여기선 HTTP 요청이 완료되고 컴포넌트의 상태(suggestData)가 업데이트되면서 리렌더링이 트리거되는 경우가 일반적이다. 상태 업데이트 → 리렌더링 → 새로운 함수 생성 → useEffect 실행 → 상태 업데이트 무한 루프인 것이다.

해결 방법

사실 그냥 eslint 경고 무시하는 코드를 짜면 되긴 한다.

useEffect(() => {
  loadSuggestData();
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

이럴거면 벨로그 키지도 않았다. 그럼 어떻게 할까? 바로 함수를 메모이제이션하면 되는 것이다. React.Memo 를 사용해도 되고 useCallback을 사용해도 좋다. 필자는 useCallback을 사용해봤다.

//loadSuggestData.js
import axios from "axios";
import { useCallback, useState } from "react"

const useLoadSuggestData = () => {
  const [데이터, set데이터] = useState([]);

  const loadSuggestData = useCallback (async () => {
    try {
      const response = await axios.get('api 요청 url')
      set데이터(response);
      console.log("추천 데이터 받아오기 성공")
    } catch (error) {
      console.error(error);
    }
  }, [])
  return { suggestData, loadSuggestData };
}

export default useLoadSuggestData;

이렇게 코드를 짜버리면, loadSuggestData 함수가 메모이제이션되어 리렌더링 시에도 동일한 참조를 유지하게 된다. useEffect 의존성 배열에 추가해도 좋단 소리다.

useEffect(() => {
  loadSuggestData();
}, [loadSuggestData]);