React.js + Next.js/번역하기

02. useMomo

진호우 2024. 1. 13. 14:43

useMemo 는 재렌더링 간에 계산 결과를 cache할 수 있는 리액트 훅이다.

const cachedValue = useMemo(calculateValue, dependencies)
  • Useage
    • 비용이 비싼 재계산 skip
    • 컴포넌트 re-rendering skip
    • 다른 Hook의 의존성 기억
    • 함수 기억
  • Reference
    • useMemo(calculateValue, dependencies)

Useage

비용이 비싼 재 계산 skip

 

re-rendering 간에 계산을 cache 하려면 useMemo 구성 요소의 최상위 수준에서 호출로 감싼다.

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tabs]);
  // ...
}

useMemo 엔 두 가지를 전달해야 한다.

  1. ( )=> 과 같이 인수를 받지 않는 함수, return
  2. 계산 내에서 사용되는 구성 요소 내의 모든 값을 포함하는 dependencies

 

첫 번째 render에서 useMemo에서 얻을 수 있는 값은 계산 함수의 결과 값이다.

이후의 render에서 React는 dependencies들을 마지막에 전달 받은 dependencies들 과 비교한다.

만약 변화가 없다면 useMemo 는 이미 계산된 결과값을 return한다. 아니라면 새로 계산하여 값을 return한다.

 

다시 말해 useMemo는 dependencies들이 변화할때까지 계산된 결과를 저장한다.

 

example)

TodoList가 새로운 props를 받거나 state가 update되면, filterTodos 함수 또한 재 실행 될 것이다.

function TodoList({ todos, tab, theme }) {
  const visibleTodos = filterTodos(todos, tab);
  // ...
}

 

대부분의 경우에서는 빠를 수 있지만, 긴 배열이나 비용이 많이드는 계산에서는 성능이 저하될 수 있다.

useMemo 는 성능최적화로만 사용해야한다. useMemo 가 없이도 코드가 작동하지 않으면, 다른 문제를 수정해야한다. 그 뒤 퍼포먼스 향상을 위해 useMemo를 사용하다.

 

 

‘비용이 많이든다’의 정의가 무엇인가?

console log를 추가하여 시간을 측정해보자

console.time('filter array'); const visibleTodos = filterTodos(todos, tab); console.timeEnd('filter array');

1ms가 넘을경우 useMemo사용을 권장한다.


또 useMemo 는 첫번째 render를 빠르게 만들지 않는다. 단지 불필요한 업데이트만 건너뛰게 할 뿐이다.

컴포넌트들의 re-rendering skip

useMemo는 자식 컴포넌트 re-rendering의 성능을 향상시킨다.

아래는 TodoList 컴포넌트에서 List컴포넌트로 visibleTodos를 prop로 전달한다.

export default function TodoList({ todos, tab, theme }) {
  // ...
  return (
    <div className={theme}>
      <List items={visibleTodos} />
    </div>
  );
}
 

컴포넌트가 re-render할때, React는 모든 자식컴포넌트들을 re-render 한다.

TodoList가 re-render할때마다 List도 re-render한다는 의미다. 만약 List를 re-render할때 느리다면 memo를 통해 skip해주도록 하자.

import { memo } from 'react';

const List = memo(function List({ items }) {
  // ...
});

 

위와같이 변경하면 props가 동일할 경우 re-render를 skip한다.

export default function TodoList({ todos, tab, theme }) {
  // Every time the theme changes, this will be a different array...
  const visibleTodos = filterTodos(todos, tab);
  return (
    <div className={theme}>
      {/* ... so List's props will never be the same, and it will re-render every time */}
      <List items={visibleTodos} />
    </div>
  );
}

 

위 같은 경우엔, vicibleTodos가 항상 새로운 배열을 생성해서 이전에 만들어둔 memo가 제 역할을 못한다.

useMemo를 어디서 사용하는지 중요하다.

export default function TodoList({ todos, tab, theme }) {
  // Tell React to cache your calculation between re-renders...
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab] // ...so as long as these dependencies don't change...
  );
  return (
    <div className={theme}>
      {/* ...List will receive the same props and can skip re-rendering */}
      <List items={visibleTodos} />
    </div>
  );
}

 

다른 Hook의 의존성 기억

컴포넌트에서 직접 생성된 객체에 의존하는 계산이 있다고 하자.

function Dropdown({ allItems, text }) {
  const searchOptions = { matchMode: 'whole-word', text };

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
  // ...

 

searchOptions가 새로 생성될때마다 re-render된다. 이때 searchOptions를 useMemo로 감싸주자.

function Dropdown({ allItems, text }) {
  const searchOptions = useMemo(() => {
    return { matchMode: 'whole-word', text };
  }, [text]); // ✅ Only changes when text changes

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
  // ...
 

더 깔끔하게 아래와 같이 바꾸는것이 좋겠다.

function Dropdown({ allItems, text }) {
  const visibleItems = useMemo(() => {
    const searchOptions = { matchMode: 'whole-word', text };
    return searchItems(allItems, searchOptions);
  }, [allItems, text]); // ✅ Only changes when allItems or text changes
  // ...

 

함수 기억

Form컴포넌트가 memo로 감싸져있고 props로 함수를 넘긴다고 가정하자.

export default function ProductPage({ productId, referrer }) {
  function handleSubmit(orderDetails) {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails
    });
  }

  return <Form onSubmit={handleSubmit} />;
}

 

useMemo로 함수를 감싸보자.

export default function Page({ productId, referrer }) {
  const handleSubmit = useMemo(() => {
    return (orderDetails) => {
      post('/product/' + product.id + '/buy', {
        referrer,
        orderDetails
      });
    };
  }, [productId, referrer]);

  return <Form onSubmit={handleSubmit} />;
}

함수와 같은경우는 위보단 useCallback을 사용하자.

export default function Page({ productId, referrer }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + product.id + '/buy', {
      referrer,
      orderDetails
    });
  }, [productId, referrer]);

  return <Form onSubmit={handleSubmit} />;
}

이전과 동일한 기능이지만, useMemo를 사용할때처럼 중첩함수를 사용하지 않아도 된다.


Reference

useMemo(calculateValue, dependencies)

memo된 값을 선언하려면 useMemo를 구성요소의 최상위에서 호출한다.

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  // ...
}

 

매개변수

  • caculateValue : 메모하고 싶은 값을 계산하는 함수. 순수함수여야 한다. React는 초기 렌더링 중에 함수를 호출한다. 이후에 변경되지 않은 경우 동일한 값을 다시 반환한다. dependencies가 동일하지 않으면 다시 호출하여 결과를 반환하고 나중에 재사용 할 수 있도록 저장한다.
  • dependencies : calculateValue 코드에서 참조하는 모든 value들. 반응형 값에는 props, state 및 구성요소에 직접 선언된 모든 변수와 함수가 포함된다. [dep1, dep2, dep3]과 같이 작성되어야 한다.

Returns

초기 렌더링에서 useMemo는 인수 없이 호출한 calculateValue결과를 반환한다.

후속 렌더링 중에 마지막 렌더링에서 종속성이 변경되지 않은 경우 이미 저장된 값을 반환하거나 변경이 되었다면, calculateValue다시 호출하고 반환 된 결과를 calculateValue반환한다.

 

주의

  • useMeme는 Hook이므로 컴포넌트의 최상위 또는 자체 Hook에서만 호출할 수 있다. 반복문이나 조건문에서는 호출 불가.
  • React는 특별하지 않은 경우 cache된 값을 버리지 않는다.

'React.js + Next.js > 번역하기' 카테고리의 다른 글

03. useState  (0) 2024.04.02
01. memo  (0) 2023.12.04