티스토리 뷰

프론트엔드

useCallback과 useMemo

jfmam 2023. 11. 20. 18:36

useCallback 과 useMemo

문제

  • useCallback과 useMemo는 캐싱을 통해 최적화를 하는데 모든 변수, 함수에 왜 안쓰나?
  • 이유가 있다면 언제 써야 하나?

useMemo: 이전에 계산된 값을 기억하여 더욱 빠르게 값을 반환한다. dependency가 변경될 때 함수가 실행된다.

useCallback: useMemo는 값을 기억하고 있다면 useCallback은 함수를 기억한다. 작동방식은 useMemo와 같다.

useCallback, useMemo 모두 이전에 사용된 값들을 저장하고 있기 때문에 오히려 무분별하게 사용할 경우 최적화가 되지 못하고 가비지 컬렉터가 되지 못해 메모리 사용측면에서 비효율적이 된다.

그렇기 때문에 언제 사용해야 할지 아는 것이 중요하다.

해결방법

일반적으로 아티클을 찾아보면 다음과 같은 경우에 사용하라고 나와있다.

https://goongoguma.github.io/2021/04/26/When-to-useMemo-and-useCallback/

 

When to useMemo and useCallback (번역)

최적화에는 비용이 있기 마련이며 무조건 유익한것은 아닙니다. 이 글에서는 useMemo와 useCallback을 사용함으로써 발생되는 비용과 혜택을 설명해보겠습니다

goongoguma.github.io

  • 참조 동일성
  • 비용이 많이드는 계산

참조 동일성

참조 동일성이란

참조 동일성(Reference Equality)은 두 객체 또는 값이 같은 메모리 주소를 참조하는 것을 말한다.

dependency에 들어가는 객체,함수,리스트의 경우 같은 새로운 인스턴스를 만들게 되는 경우 useCallback, useMemo도 계속 실행이 될 것이고, 변수나 함수를 props로 보내주게 되는 경우에도 React.memo를 사용해도 재랜더링이 발생한다.

이런 점을 방지하고자 dependency, props에 사용되는 값을 useMemo나 useCallback을 사용하여 참조 동일성을 유지하는 방법이 있다.

function Foo({ bar, baz }) {
  React.useEffect(() => {
    const options = { bar, baz };
    buzz(options);
  }, [bar, baz]);
  return <div>foobar</div>;
}

function Blub() {
  const bar = React.useCallback(() => {}, []);
  const baz = React.useMemo(() => [1, 2, 3], []);
  return <Foo bar={bar} baz={baz} />;
}

예시

  • value에 전달하는 contextValue는 객체이기 때문에 만약 같은 key-value를 갖더라도 새로운 인스턴스를 전달하게 되면 다르다고 인식하여 ConsumerComponent도 재 렌더링이 발생한다.
import React, { useMemo, useState } from 'react';
const ProductContext = React.createContext();
const ConsumerComponent = ({ value }) => {
  return <div>{value.id} - {value.name}</div>;
};

const ParentComponent = () => {
  const [id, setId] = useState(1);
  const [name, setName] = useState('Product A');

  // useMemo를 사용하여 매번 객체가 새로 생성되는 것을 방지합니다.
  const contextValue = useMemo(() => {
    return { id, name };
  }, [id, name]); 

  return (
    <div>
      <ProductContext.Provider value={contextValue}>
        {/* 여러 개의 Consumer를 렌더링할 수 있습니다. */}
        <ConsumerComponent />
        <ConsumerComponent />
      </ProductContext.Provider>
    </div>
  );
};

복잡한 계산 비용이 발생하는 경우

  • 계산 비용이 많이 들고, 사용자의 입력 값이 map과 filter을 사용했을 때와 같이 이후 렌더링 이후로도 참조적으로 동일할 가능성이 높은 경우, useMemo를 사용하는 것이 좋다.
    •  
    • 외부 요청 API
    • 대규모 데이터 처리
    • 알고리즘, 캐싱
import React, { useState, useMemo } from 'react';

const ExpensiveCalculationComponent = ({ data }) => {
  const expensiveCalculation = (data) => {
    // 아주 복잡한 계산을 하는 함수라고 가정합니다.
    console.log('Expensive calculation executed!');
    return data.map(item => item * 2); // 예시를 위해 각 항목을 2배로 만듭니다.
  };

  // useMemo를 사용하여 이전 결과를 재사용합니다.
  const processedData = useMemo(() => expensiveCalculation(data), [data]);

  return (
    <div>
      <h2>Expensive Calculation Result:</h2>
      <ul>
        {processedData.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

const App = () => {
  const [inputData, setInputData] = useState([1, 2, 3, 4, 5]);

  return (
    <div>
      <button onClick={handleInputChange}>Change Input Data</button>
      <ExpensiveCalculationComponent data={inputData} />
    </div>
  );
};

export default App;
  • 매우 큰 리액트 트리 구조 내에서, 부모가 리렌더링 되었을 때 이에 다른 렌더링 전파를 막고 싶을 때 사용하자.

그 외에 사용해야 하는 경우

https://yceffort.kr/2022/04/best-practice-useCallback-useMemo

 

리액트의 useCallback useMemo, 정확하게 사용하고 있을까

Table of Contents Introduction 리액트 코드를 리뷰하다보면, useCallback과 useMemo를 정말 많은 곳에 사용하는 것을 발견하게 된다. 일반적으로 두 훅을 쓰게 되는 이유는 컴포넌트에 무언가 함수를 전달할

yceffort.kr

  1. ref 함수를 부수작용과 함께 전달하거나, mergeRef-style 과 같이 wrapper 함수 ref를 만들 때 useMemo를 쓰자. ref 함수가 변경이 있을 때마다, 리액트는 과거 값을 null로 호출하고 새로운 함수를 호출한다. 이 경우 ref 함수의 이벤트 리스너를 붙이거나 제거하는 등의 불필요한 작업이 일어날 수 있다. 예를 들어, useIntersectionObserver가 반환하는 ref의 경우 ref 콜백 내부에서 observer의 연결이 끊기거나 연결되는 등의 동작이 일어날 수 있다.
import React, { useRef, useEffect, useMemo } from 'react';
const useIntersectionObserver = (callback) => {
  const observer = useRef(null);

  useEffect(() => {
    observer.current = new IntersectionObserver((entries) => {
      callback(entries);
    });

    return () => {
      if (observer.current) {
        observer.current.disconnect(); // 이전 observer 연결 해제
      }
    };
  }, [callback]);

  return observer;
};

const ComponentUsingRef = () => {
  const myRef = useRef(null);
.
  const observerRef = useMemo(() => {
    return useIntersectionObserver((entries) => {
      console.log('Intersection Observer Callback:', entries)
    });
  }, []);

  useEffect(() => {
    if (myRef.current && observerRef.current) {
      observerRef.current.observe(myRef.current);
    }

    return () => {
      if (observerRef.current) {
        observerRef.current.disconnect(); // 컴포넌트 언마운트 시 관찰 중지
      }
    };
  }, [observerRef]);

  return <div ref={myRef}>Intersection Observer Example</div>;
};

export default ComponentUsingRef;

Reference

https://yceffort.kr/2022/04/best-practice-useCallback-useMemo

 

리액트의 useCallback useMemo, 정확하게 사용하고 있을까

Table of Contents Introduction 리액트 코드를 리뷰하다보면, useCallback과 useMemo를 정말 많은 곳에 사용하는 것을 발견하게 된다. 일반적으로 두 훅을 쓰게 되는 이유는 컴포넌트에 무언가 함수를 전달할

yceffort.kr

https://goongoguma.github.io/2021/04/26/When-to-useMemo-and-useCallback/

 

When to useMemo and useCallback (번역)

최적화에는 비용이 있기 마련이며 무조건 유익한것은 아닙니다. 이 글에서는 useMemo와 useCallback을 사용함으로써 발생되는 비용과 혜택을 설명해보겠습니다

goongoguma.github.io

 

'프론트엔드' 카테고리의 다른 글

[React] suspense를 써야하는 이유  (1) 2023.12.30
[css] float  (2) 2023.11.01
스토리북 설치하기  (0) 2023.03.19