useCallback

hojoon·2023년 5월 10일
0

리액트

목록 보기
3/10

함수 메모이제이션

useCallback()은 함수를 메모이제이션(memoization)하기 위해서 사용되는 hook 함수입니다. 첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해줍니다.

const useCallbackFunc = useCallback(()=>{},[])

useEffect와 비슷하게 첫번째 인자로 함수를, 두번째 인자로 의존성 배열을 받는다.

의존성 배열

안에 담겨있는 값에 변화가 생기면 함수가 실행되는데 예를 들어

const add = () => x+ y;

이 함수가 선언이 되어 있는 컴포넌트가 있다고 생각해보자. 이 함수는 해당 컴포넌트가 렌더링 될 때 마다 생성되고 실행될 것 이다.

하지만 useCallback 훅을 사용하면 해당 컴포넌트가 렌더링되더라도 의존성 배열안에 담긴 값이 바뀌지 않는 이상 기존 함수를 계속해서 반환한다.

const add = useCallback (()=> x +  y, [x,y]);

의존성 배열안에 담긴 x,y 값이 변경되지 않는 이상 같은 함수를 리턴한다.

하지만

렌더링될 때마다 함수를 실행하는것은 사실 엄청나게 많은 동작을 포함한 컴포넌트가 아닌 이상 속도적인 측면에서 큰 문제가 되지 않는다. 이전에 useMemo훅을 봤을 때와 같이 오히려 값을 가지고 있어야 하기 때문에 메모리공간을 쓰면서 성능면에서 더 안좋을 수도 있다.
그럼 왜 쓸까??

우선 자바스크립트 함수에 대해 이해할 필요가 있다.

const add1 = () => x + y;
// undefined
const add2 = () => x + y;
// undefined
add1 === add2
// false

자바스크립트에서 함수도 객체로 취급이 되기 때문에 메모리 주소에 의한 참조 비교가 일어나기 때문이다.
이러한 자바스크립트 특성은 React 컴포넌트 함수 내에서 어떤 함수를 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 문제가 발생할 수 있다.

프로젝트를 진행하면서 useEffect를 쓰다보면 가끔 예상치 못한 무한렌더링 문제가 발생하고는 한다.

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

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = () =>
    fetch(`https://your-api.com/users/${userId}`)
      .then((response) => response.json())
      .then(({ user }) => user);

  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ...
}

마찬가지로 컴포넌트가 렌더링될 때 마다 새로운 참조값으로 변경이 되기 때문에 useEffect 함수가 호출되어 user 상태값이 바뀌고 그러면 다시 렌더링이 되고 그럼 또 다시 useEffect가 호출되는 악순환이 발생한다.

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

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  const fetchUser = useCallback(
    () =>
      fetch(`https://your-api.com/users/${userId}`)
        .then((response) => response.json())
        .then(({ user }) => user),
    [userId]
  );

  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ...
}

이렇게 useCallback을 사용할 수 있다.

예제로 알아보자.

import React from "react";

function Light({ room, on, toggle }) {
  console.log({ room, on });
  return (
    <button onClick={toggle}>
      {room} {on ? "💡" : "⬛"}
    </button>
  );
}
import React, { useState, useCallback } from "react";

function SmartHome() {
  const [masterOn, setMasterOn] = useState(false);
  const [kitchenOn, setKitchenOn] = useState(false);
  const [bathOn, setBathOn] = useState(false);

  const toggleMaster = () => setMasterOn(!masterOn);
  const toggleKitchen = () => setKitchenOn(!kitchenOn);
  const toggleBath = () => setBathOn(!bathOn);

  return (
    <>
      <<Light room="침실" on={masterOn} toggle={toggleMaster} />
      <Light room="주방" on={kitchenOn} toggle={toggleKitchen} />
      <Light room="욕실" on={bathOn} toggle={toggleBath} />
    </>
  );
}

이 코드의 문제점은 ?

  • 우선 버튼을 눌렀을 때 불이 켜지고 꺼지고 두가지 동작을 한다.
  • 각각 침실, 주방, 욕실로 분리되어 있다.

문제는 침실의 조명을 켜보면 주방과 욕실에서도 console.log가 실행된다. 왜냐면 참조값이 모두 바뀌기 때문
이러한 문제를 해결하는것이 useCallback이다.

import React, { useState, useCallback } from "react";

export default function SmartHome() {
  const [masterOn, setMasterOn] = useState(false);
  const [kitchenOn, setKitchenOn] = useState(false);
  const [bathOn, setBathOn] = useState(false);

  const toggleMaster = useCallback(() => setMasterOn(!masterOn), [masterOn]);
  const toggleKitchen = useCallback(
    () => setKitchenOn(!kitchenOn),
    [kitchenOn]
  );
  const toggleBath = useCallback(() => setBathOn(!bathOn), [bathOn]);

  return (
    <>
      <Light room="침실" on={masterOn} toggle={toggleMaster} />
      <Light room="주방" on={kitchenOn} toggle={toggleKitchen} />
      <Light room="욕실" on={bathOn} toggle={toggleBath} />
    </>
  );
}

각 각 useCallback으로 감싸주고 의존성 배열안에 의존하고 있는 상태를 넘겨줘야 한다. 이러며 원하는 동작을 할 것 이다!!

다음글은 useCallback에 대해 공부하면서 가볍게 넘어갔던 의존성 배열에 대한 이해도를 높여야겠다는 생각과 useEffect에 대한 공부가 필요함을 깨달았기 때문에 useEffect글로 찾아오겠습니다.!

profile
프론트? 백? 초보 개발자의 기록 공간

0개의 댓글