[React] useCallback 사용법

useCallback 사용법

Posted by lim.Chuck on October 31, 2024

[React]

  1. [React] vite React설치
  2. Project initial setting
  3. Hooks && Library
    1. [React] Javascript 파일을 Typescript Import해보자
    2. [React] react-router-dom 알아보고 사용하자
    3. [React] child 컴포넌트에서 함수호출하기
    4. [React] useCallback
    5. [React] framer-motion 알아보고 사용하자
    6. [React] Context API
    7. [React] react lazy 지연로딩 예제
    8. [React] react에서 캐싱처리를 위한 react queryd와 indexDB 비교분석
    9. [React] 스타일 라이브러리 styled-components stitches 비교
  4. [React] 프론트 에러추적 도구 Sentry 사용해보기
  5. [React] React 19 주요 변경점
  6. [React] DOM이란 무엇이고? 가상 DOM이란 무엇인가?
  7. [React] preact 알아보기

useCallback는 React의 훅 중 하나로, 메모이제이션된 콜백 함수를 반환하는 역할을 합니다. useCallback을 활용하면 컴포넌트가 불필요하게 재렌더링되지 않도록 최적화할 수 있어 성능 향상에 유용합니다.

언제 useCallback을 사용해야 할까요?

  • 함수 재생성 방지: 컴포넌트가 재렌더링될 때마다 함수들이 새로 생성되는데, 이는 성능에 영향을 줄 수 있습니다. 특히, 자식 컴포넌트에 콜백 함수를 props로 전달할 때 useCallback을 사용하면 재생성을 막을 수 있습니다.
  • 비용이 큰 연산 방지: 특정 함수가 실행될 때 많은 연산을 한다면, useCallback을 통해 필요할 때만 해당 함수를 다시 생성하여 리소스 사용을 줄일 수 있습니다.
  • 의존성 배열 사용: useCallback은 의존성 배열을 사용하여, 배열 안에 정의된 값이 변경될 때만 함수를 다시 생성합니다.

코드 예제

  • useCallback 사용하지않을때 렌더링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { useState, useCallback, ChangeEvent } from "react";

// 자식 컴포넌트 추가
const TextInput = React.memo(
  ({
    value,
    onChange,
  }: {
    value: string,
    onChange: (e: ChangeEvent<HTMLInputElement>) => void,
  }) => {
    console.log("3.TextInput 컴포넌트 렌더링");
    return <input type="text" value={value} onChange={onChange} />;
  }
);

function Counter() {
  console.log("2.Counter 컴포넌트 렌더링");
  const [count, setCount] = useState < number > 0;
  const [text, setText] = useState < string > "";
  const addCount = () => {
    console.log("1. addCount 호출");
    setCount((prev) => prev + 1);
  };

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log("handleChange 호출");
    setText(e.target.value);
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={addCount}>Increment</button>
      <TextInput value={text} onChange={handleChange} />
    </div>
  );
}

export default Counter;

addCount() 호출하는데 컴포넌트가 렌더링되는 것을 볼수있음 그러면서 input에 입력하는 것도 렌더링되는 것을 볼수있음 불필요하게 같이 렌더링됨

  • useCallback 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { useState, useCallback, ChangeEvent } from "react";

// 자식 컴포넌트 추가
const TextInput = React.memo(
  ({
    value,
    onChange,
  }: {
    value: string,
    onChange: (e: ChangeEvent<HTMLInputElement>) => void,
  }) => {
    console.log("3.TextInput 컴포넌트 렌더링");
    return <input type="text" value={value} onChange={onChange} />;
  }
);

function Counter() {
  console.log("2.Counter 컴포넌트 렌더링");
  const [count, setCount] = useState < number > 0;
  const [text, setText] = useState < string > "";
  const addCount = () => {
    console.log("1. addCount 호출");
    setCount((prev) => prev + 1);
  };
  // useCallback 사용
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log("handleChange 호출");
    setText(e.target.value);
  };

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={addCount}>Increment</button>
      <TextInput value={text} onChange={handleChange} />
    </div>
  );
}

export default Counter;

불필요한 렌더링이 없어져서 최적화에 좋음

네, useCallback을 사용하지 않은 버전과 사용한 버전을 비교하여 다시 작성해드리겠습니다.

// useCallback 사용하지 않은 버전
function Counter() {
  // ... 기존 코드 ...

  const addCount = () => {
    console.log("1. addCount 호출");
    setCount((prev) => prev + 1);
  };

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log("handleChange 호출");
    setText(e.target.value);
  };

  // ... 기존 코드 ...
}

// useCallback을 사용한 버전
function Counter() {
  // ... 기존 코드 ...

  const addCount = useCallback(() => {
    console.log("1. addCount 호출");
    setCount((prev) => prev + 1);
  }, []); // 의존성 배열이 비어있으므로 컴포넌트가 마운트될 때만 함수 생성

  const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    console.log("handleChange 호출");
    setText(e.target.value);
  }, []); // 의존성 배열이 비어있으므로 컴포넌트가 마운트될 때만 함수 생성

  // ... 기존 코드 ...
}

주요 변경사항 설명:

  1. addCount 함수:

    • 변경 전: 컴포넌트가 리렌더링될 때마다 새로운 함수가 생성됨
    • 변경 후: useCallback으로 메모이제이션되어 컴포넌트가 마운트될 때만 함수가 생성됨
  2. handleChange 함수:

    • 변경 전: text가 변경될 때마다 새로운 함수가 생성됨
    • 변경 후: useCallback으로 메모이제이션되어 컴포넌트가 마운트될 때만 함수가 생성됨

이러한 변경으로 인해:

  • 불필요한 함수 재생성이 방지됨
  • TextInput 컴포넌트의 불필요한 리렌더링이 방지됨
  • 전반적인 성능이 최적화됨

요약

  1. useCallback을 사용하는 경우: 특정 함수가 자식 컴포넌트에 props로 전달되거나, 자주 호출되며 재생성 비용이 큰 경우.
  2. 의존성 관리: useCallback의 두 번째 인수로 의존성 배열을 전달하여, 필요한 경우에만 함수가 재생성되도록 합니다.