useCallback에 대해서

Kim Jason·2023년 4월 1일
0

React Hooks

목록 보기
2/3
post-thumbnail

useCallback

함수에 대해 메모이제이션을 수행해 컴포넌트 렌더링 성능을 최적화 합니다.

🚪 들어가기 전에

원활한 이해를 위해 몇 가지 짚고 넘어가자.

함수형 컴포넌트

함수형 컴포넌트도 결국엔 함수다.
함수형 컴포넌트가 렌더링 되는 것은 함수를 호출하는 것과 같은 의미다.
함수를 호출하면 내부의 모든 변수가 초기화 된다는 당연한 사실을 되새겨보자.

useCallback 구조

const exFunc = useCallback((num) => {
  return num + 1;
}, [item]);

useCallback은 useMemo와 유사하다.
하지만 useCallback은 인자로 전달한 콜백함수 그 자체를 메모이제이션 한다.
원하는 함수를 useCallback으로 감싸주기만 하면 된다.

자바스크립트 함수는 객체

자바스크립트 함수를 변수에 할당할 수 있다는 당연한 사실을 되새겨보자.
따라서 특정 함수에 useCallback을 적용하면 컴포넌트가 재렌더링 될 때 해당 변수는 새로운 함수 객체를 할당받지 않을 수 있다.

🧪 예제 1

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

function App() {
  const [number, setNumber] = useState(0);
  const [toggle, setToggle] = useState(true);
  
  const exFunc = useCallback(() => {
    console.log(`exFunc: number: ${number}`);
    return;
  }, [number]);
  
  useEffect(() => {
    console.log("exFunc 변경됨!");
  }, [exFunc]);
  
  return (
    <div>
      <input
        type="number"
        value={number}
        onChange={e => setNumber(e.target.value)}
      />
      <button onClick={() => setToggle(prev => !prev)>
        {toggle.toString()}
      </button>
      <br />
      <button onClick={exFunc}>Call exFunc</button>
    </div>
  );
}

number 상태를 변경하면 다음과 같은 일들이 발생한다.

  • number 상태 업데이트 > App 함수(컴포넌트) 재실행 > 변수 exFunc에 새로운 함수 객체 할당 > useEffect 실행

결과적으로 number 상태가 업데이트 될 때마다 콘솔에 "exFunc 변경됨!" 로그가 찍힌다.

하지만 토글 버튼을 눌러 toggle 상태를 업데이트하면 어떨까?
이때는 number 상태가 업데이트 되지 않기 때문에 useEffect는 실행되지 않는다.
변수 exFunc가 메모이제이션 된 함수 객체만을 바라보기 때문이다.

🧪 예제 2

문제

이번에는 보다 더 현실적인 예제를 살펴보자.
App 컴포넌트에서 Box 컴포넌트로 createBoxStyle 함수를 prop의 형태로 넘기고 있다.

// ✅ App.js

import { useState } from "react";
import "./App.css";
import Box from "./components/Box";

function App() {
  const [size, setSize] = useState(100);
  const [isDark, setIsDark] = useState(false);

  const createBoxStyle = () => ({
    backgroundColor: "pink",
    width: `${size}px`,
    height: `${size}px`,
  });

  return (
    <div style={{ background: isDark ? "black" : "white" }}>
      <input
        type="number"
        value={size}
        onChange={e => setSize(e.target.value)}
      />
      <button onClick={() => setIsDark(prev => !prev)}>Change Theme</button>
      <Box createBoxStyle={createBoxStyle} />
    </div>
  );
}

export default App;
// ✅ Box.js

import { useEffect, useState } from "react";

function Box({ createBoxStyle }) {
  const [style, setStyle] = useState({});

  useEffect(() => {
    console.log("박스야 놀자 📦");
    setStyle(createBoxStyle());
  }, [createBoxStyle]);

  return <div style={style} />;
}

export default Box;

버튼을 클릭해서 isDark 상태를 업데이트 하면 다음과 같은 일들이 발생한다.

  • isDark 상태 업데이트 > 변수 createBoxStyle에 새로운 함수 객체가 할당 > Box 컴포넌트로 새로운 createBoxStyle 함수 전달 > Box 컴포넌트 내 useEffect 실행

isDark 상태가 업데이트 됐는데(=다크모드 여부가 바뀌었는데) Box 컴포넌트 내 useEffect가 실행되는 것은 이상하다.
독립 관계이어야 하는 서로 관련 없는 요소들이 의존하고 있는 상황이다.
이 문제는 어떻게 해결할 수 있을까?

해결 방법

const createBoxStyle = useCallback(
    () => ({
      backgroundColor: "pink",
      width: `${size}px`,
      height: `${size}px`,
    }),
    [size]
  );

App 컴포넌트 내 createBoxStyle 함수를 useCallback으로 감싸면 된다.
이때 size 상태가 업데이트 될 때만 변수 createBoxStyle에 새로운 함수 객체가 할당될 수 있또록 의존성 배열에 size 상태를 주입한다.
이렇게 함수에 useCallback을 적용하면 Box 컴포넌트 내 useEffect는 더이상 실행되지 않는다.

profile
성장지향형 프론트엔드 개발자

0개의 댓글