React에서 컴포넌트가 재랜더링될 때, 그 컴포넌트 내에 정의된 모든 함수들은 새로 생성됩니다. 

특히, 부모 컴포넌트에서 정의된 함수가 자식 컴포넌트로 props로 전달될 때, 

부모 컴포넌트가 재렌더링되면 해당 함수는 새로운 참조를 가지게 됩니다. 

그러면 자식 컴포넌트는 props가 변경되었다고 인식하여 불필요한 재랜더링을 하게 됩니다.

이러한 문제는 자바스크립트의 함수가 클로저(closure)라는 특성 때문에 발생하는데,

클로저는 함수가 생성된 시점의 환경을 '기억' 합니다.

 

이 문제를 해결하기 위해 react에서는 useCallback이라는 Hook을 사용합니다.

useCallback을 사용하면 useCallback 첫번째 인자로 전달된 함수를 메모이제이션(저장)하고, 

A 컴포넌트가 재렌더링될 때마다 새로 생성되지 않고,

의존성 배열에 명시된 값이 변경될 때만 함수가 새로 생성됩니다. 

따라서 이를 통해 불필요한 B 컴포넌트의 재렌더링을 방지할 수 있습니다.

 

그럼 useCallback을 어떤 상황에서 사용하면 효율적인지 코드로 살펴보겠습니다.

코드에 구현한 기능은 엑셀파일 다운로드 기능입니다.

statistics컴포넌트에 monthExportFile함수를 정의하고 그 함수를 자식컴포넌트인 MonthlyList 컴포넌트의

props로 전달하고 있습니다.

만약에 useCallback을 사용하지 않았다면 부모 컴포넌트가 어떤 이유로든 재렌더링 될 때마다

monthExportFile 함수는 새롭게 생성되고, 그로 인해 이 함수를 props으로 받은 자식 컴포넌트들도

재렌더링 될 수 있습니다. 왜냐하면 리액트는 함수의 새로운 인스턴스를 새로운 prop으로 보기 때문입니다.

 

이걸 방지하기위해 useCallback을 사용하여 monthConvertData를 의존성배열에 넣어서 저 값이 바뀔때에만

함수가 재생성되도록 하였습니다. 

 

예시코드 📍

////Statistics 컴포넌트 (부모 컴포넌트)


//state hook
const [monthSemesterSumTotal, setMonthSemesterSumTotal] = useState<MonthlyData[]>([]);
const [monthFirstSemesterData, setMonthFirstSemesterData] = useState<SemesterData | null>(null);
const [monthSecondSemester, setMonthSecondSemester] = useState<SecondSemesterData | null>(null);

//변수 지정
const monthFirstSemesterRaw = monthFirstSemesterData.firstSemesterData;
const monthFirstSemesterTotal = monthFirstSemesterData.total;
const monthSecondSemesterRaw = monthSecondSemester.secondSemesterData;
const monthSecondSemesterTotal = monthSecondSemester.secondTotal;

const monthAddSemesterData = monthFirstSemesterRaw?.concat(
monthFirstSemesterTotal,
monthSecondSemesterRaw,
monthSecondSemesterTotal
);

const monthAddSemesterExcelData = monthAddSemesterData?.concat(
monthSemesterSumTotal
);

//monthAddSemesterExcelData의 제목을 한글로 변경해주는 함수
const monthConvertData = monthAddSemesterExcelData?.map((pre: MonthlyData) => {
    if (pre) {
      return {
        월별: pre.month,
        "1학년": pre.grade1,
        "2학년": pre.grade2,
        "3학년": pre.grade3,
        총상담학생수: pre.totalCounselee,
        총상담시간: pre.totalTime,
        주당상담시간: pre.timePerWeek,
      };
    }
  });
  
  
 //최종 결과물을 엑셀파일로 만들어주는 함수
 const monthExportFile = useCallback(() => {
    const toExcel = utils.json_to_sheet(monthConvertData);
    const workSheet = utils.book_new();
    utils.book_append_sheet(workSheet, toExcel, "Data");
    writeFileXLSX(workSheet, "학생상담 월별 리스트.xlsx");
 }, [monthConvertData]);
 
 return(
  <div>
   <MonthlyList
      monthExportFile={monthExportFile}
      monthFirstSemesterRaw={monthFirstSemesterRaw}
      monthFirstSemesterTotal={monthFirstSemesterTotal}
      monthSecondSemesterRaw={monthSecondSemesterRaw}
      monthSecondSemesterTotal={monthSecondSemesterTotal}
      monthSemesterSumTotal={monthSemesterSumTotal}
    />
  </div>
  );
 };
 
 export default Statistices;

 

 

1. API를 호출하여 1학기 데이터와 2학기 데이터를 monthFirstSemesterData 와

monthSecondSemester useState() 에 저장해주었습니다. 

2. 1학기와 2학기 데이터와 총 데이터를 저장하는 변수를 지정합니다.

3. 배열을 합쳐주는 concat 메소드를 사용하여 학기별 데이터를 결합하여 monthAddSemesterData에 저장하고 

변수에 총 상담 합계를 추가하여 monthAddSemesterExcelData에 저장합니다.

4. monthConvertData는 엑셀파일의 monthAddSemesterExcelData 데이터의 각 열 제목을 한글로 변경하는 함수입니다.

5. monthExportFile 함수는 useCallback 훅을 사용하여 월별 학생 삼당 데이터를 엑셀 파일로 내보내는 기능을 구현합니다.

6. useCallback을 사용하는 주된 이유는 불필요한 함수 재생성을 방지하고 성능 최적화를 달성하기 위함입니다.

현재 구현한 코드에서 monthExportFile 함수가 의존하는 외부 변수는 monthConvertData 입니다.

 

물론 이러한 최적화가 반드시 필요한 것은 아니며, monthExportFile 함수의 재생성이 성능에 

큰 영향을 주지 않는 경우라면, useCallback을 사용하지 않아도 상관없습니다. 

하지만 대부분의 경우에는 최적화를 위해 useCallback을 사용하는 것이 좋습니다.

복사했습니다!