[React] useCallback

rsuubinn·2023년 3월 23일
0

React

목록 보기
6/7

✏️ useCallback

useCallback 사용법

우선 간단한 예제를 살펴보자.

// App.js
import React, { useState } from "react";
import List from "./List";

function App() {
  const [number, setNumber] = useState(1);
  const [dark, setDark] = useState(false);

  const getItems = () => {
    return [number, number + 1, number + 2];
  };

  const theme = {
    backgroundColor: dark ? "#333" : "#fff",
    color: dark ? "#fff" : "#333"
  };
  return (
    <div style={theme}>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(parseInt(e.target.value))}
      />
      <button onClick={() => setDark((prev) => !prev)}>테마 변경</button>
      <List getItems={getItems} />
    </div>
  );
}

export default App;
// List.js
import React, { useEffect, useState } from "react";

function List({ getItems }) {
  const [items, setItems] = useState([]);
  useEffect(() => {
    setItems(getItems());
    console.log("숫자가 변동되었습니다.");
  }, [getItems]);
  return items.map((item, index) => <div key={index}>{item}</div>);
}

export default List;

input 창을 통해 숫자를 입력할 경우 페이지의 숫자 항목들이 변경되고, theme 토글 버튼을 누를 경우 css적 요소가 변하도록 하는 예제코드 이다.

숫자를 변경해보면 콘솔 창에 '숫자가 변동되었습니다.'라는 메시지를 볼 수 있다.
하지만 테마 토글 버튼을 누를 경우에도 '숫자가 변동되었습니다.' 라는 메시지가 콘솔창에 출력된다.

테마를 변경할 뿐인데 원치 않게 getItems 함수가 실행되고 있다.

왜 이러한 현상이 발생하는가?

그 이유는 부모에게서 받는 getItems라는 함수 props가 부모 컴포넌트가 리렌더링 되면서 변경된 props로 인식되기 때문이다. 이로 인해 부모 컴포넌트의 number 값이 새로 설정되며 해당 함수가 계속 반복되어 실행되는 것이다.
useCallback을 사용하면 이를 해결할 수 있다.

number에 대한 의존성을 가지는 useCallback 으로 getItems 함수를 매핑시켜주면 될 것 같다.
아래는 react에서 제공해주는 useCallback 예시코드 이다.

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  }, [a, b]
);

이를 App.js 에 적용시켜보자.

// App.js
import React, { useCallback, useState } from "react";
import List from "./List";

function App() {
  const [number, setNumber] = useState(1);
  const [dark, setDark] = useState(false);

  const getItems = useCallback(() => {
    return [number, number + 1, number + 2];
  }, [number]);

  const theme = {
    backgroundColor: dark ? "#333" : "#fff",
    color: dark ? "#fff" : "#333"
  };
  return (
    <div style={theme}>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(parseInt(e.target.value))}
      />
      <button onClick={() => setDark((prev) => !prev)}>테마 변경</button>
      <List getItems={getItems} />
    </div>
  );
}

export default App;

이제 테마 토클 버튼을 눌러도 콘솔창에 '숫자가 변동되었습니다.' 문구가 출력되지 않는다.

이렇게 볼 때 useMemo 와 아주 흡사하다.

이 둘에겐 어떤 차이점이 존재할까?

useCallback과 useMemo의 차이점

App.js 파일의 getItems 함수에서 우리가 특정한 값을 매개변수로 받아 요소 하나하나에 더해주는 식의 코드를 작성한다고 가정해보자.

useCallback()

// App.js
const getItems = useCallback(
  (increaseValue) => {
    return [
      number + increaseValue,
      number + 1 + increaseValue,
      number + 2 + increaseValue
      ]
  }, [number]
);

useMemo()

// App.js
const getItems = useMemo(
  () => (increaseValue) => {
    return [
      number + increaseValue,
      number + 1 + increaseValue,
      number + 2 + increaseValue,
      ]
  }, [number]
);

useCallback(fn, deps) 는 useMemo(() => fn, deps)와 같다.

useMemo는 함수를 반환하지 않고, 함수의 값만 memoization해서 반환한다.
그와 다르게 useCallback은 함수 자체를 memoization 해서 반환한다.
이 부분이 useMemo와 useCallback의 핵심적인 차이점이다.

자식 컴포넌트에서 특정한 props 들의 변화를 최적화시키고 싶을 때는 useMemo를, 부모 컴포넌트에서 계산량이 많은 props 함수를 자식 컴포넌트에 넘겨줄 때는 useCallback 을 사용하는 것이 좋다고 생각된다.

참고

profile
@rsuubinn

0개의 댓글