[1011] React복습: 메모이제이션

방법이있지·6일 전
5

웹개발

목록 보기
17/18

메모이제이션

한 4달전쯤 크래프톤 정글에서 다이나믹 프로그래밍을 공부하면서 메모이제이션을 처음 접했는데요,

앞서 이미 계산한 값을 반복해서 계산하는 대신, 배열 같은 자료구조에 저장해 두고 필요할 때 다시 써먹는 방법이였죠.

리액트에서도 비슷하게 메모이제이션을 활용 가능합니다. 값/함수/컴포넌트를 메모리에 저장해두어, 불필요한 연산과 리렌더링을 방지하는 것이죠

참고 - 한 입 크기로 잘라 먹는 리액트, 인프런

useMemo - 값 최적화

const memoizedValue = useMemo(() => {
  return 계산할값;
}, [의존성]);

useMemo값의 불필요한 재계산을 방지하는Hook입니다. 처음 계산된 결과를 메모리에 저장하고, 이후 컴포넌트가 리렌더링되더라도 의존성 배열의 값이 바뀌지 않으면 재계산 없이 저장값을 그대로 반환합니다.

다만, 의존성 배열에 포함된 값이 변경되면 해당 계산을 다시 수행합니다.

import { useState, useMemo } from "react";

function Example() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  const double = useMemo(() => {
    return count * 2;
  }, [count]);

  return (
    <div>
      <p>count: {count}</p>
      <p>double: {double}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="텍스트 입력"
      />
    </div>
  );
}

위 코드 같은 경우 텍스트를 <input>에 입력해 text state가 변경되면 Example 컴포넌트가 리렌더링됩니다. 하지만 double이 재계산되지는 않습니다. 변수 count는 그대로기 때문입니다.

React.memo - 컴포넌트 최적화

export default React.memo(컴포넌트명);

React.memo컴포넌트의 불필요한 재렌더링을 방지하며, 컴포넌트에 감싸서 사용합니다. 처음 렌더링할 때 결과를 메모리에 저장하고, 이후 컴포넌트가 리렌더링되더라도 props가 변하지 않으면 저장된 렌더링 결과를 재사용합니다.

다만, props가 변경되면 컴포넌트가 재렌더링됩니다.

const Child = React.memo(({ value }) => {
  return <div>{value}</div>;
});

function Parent() {
  const [count, setCount] = useState(0);
  return (
    <>
      <Child value="고정된 값" />
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

위 코드 같은 경우 button을 클릭하면 count가 변경되어 Parent가 리렌더링되더라도, React.memo로 감싼 Child는 리렌더링되지 않습니다. 전달되는 propvalue가 그대로기 때문입니다.

useCallback - 함수 최적화

const memoizedFunc = useCallback(콜백함수, [의존성]);

useCallback함수의 불필요한 재생성을 방지하는 훅입니다. 함수도 JS 객체이기 때문에, 함수를 포함하는 컴포넌트가 리렌더링될 시 함수 내부 코드는 동일해도 별도의 객체로 재생성됩니다. 이러한 현상을 방지하기 위해서 useCallback을 사용하며, 이후 컴포넌트가 리렌더링되더라도 의존성 배열의 값이 바뀌지 않으면 함수를 다시 생성하지 않습니다.

다만, 의존성 배열에 포핟된 값이 변경되면 해당 함수를 재생성합니다.

참고로 useCallback(콜백함수, 의존성배열)useMemo(() => 콜백함수, 의존성배열)과 동일한 동작을 합니다. 하지만 깔끔한 코드를 위해선 useCallback을 쓰는 게 더 좋겠죠.

import { useState, useCallback } from "react";

function Example() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return <button onClick={increment}>+1</button>;
}

위 코드의 경우, button을 눌러 count state가 변경되어 Example 컴포넌트가 다시 렌더링되어도, increment 함수가 다시 생성되지 않습니다. 의존성 배열이 비어 있어서, 처음 렌더링될 때 빼고는 절대로 함수가 다시 생성되지 않아요.

최적화는 언제, 어떻게 해야 해요?

우선 프로젝트 기능을 구현하고, 최적화는 맨 마지막에 하는 것이 권장됩니다. 이런 최적화 방법을 처음부터 적용하면, 추가로 기능을 구현할 시 코드가 복잡해져서 유지보수가 어려워질 수 있습니다.

그리고 꼭 모든 컴포넌트/값/함수에 최적화를 하기보단, 필수적인 코드에만 하는 게 권장됩니다. 메모이제이션 시 의존성 배열의 값이나 props가 변경되었는지 확인해야 하므로 오버헤드가 발생하기 때문입니다.

최적화가 필요한 경우

  • 배열의 아이템처럼, 무한히 증가할 수 있는 컴포넌트. 개수가 많아질수록 배열을 순회하면서 렌더링할 때 성능에 영향을 줍니다.
  • 복잡한 수학 계산, 데이터 변환, 필터링 등 오래 걸리는 작업.

최적화가 불필요한 경우

  • 헤더, 푸터처럼 단순 HTML만 렌더링하는 컴포넌트
  • 리렌더링 빈도가 낮은 컴포넌트
  • 사칙연산과 같은 간단한 계산만 이루어지는 컴포넌트
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

1개의 댓글

comment-user-thumbnail
6일 전

연휴에 안 쉬고 복습이 올라온 거 ㄷㄷ 그냥 송상록을 국회로

답글 달기