[React] memo

0

React

목록 보기
5/6

memo

memo는 컴포넌트의 props가 변경되지 않았을 때 해당 컴포넌트의 다시 렌더링을 건너뛰게 해줍니다.

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

Reference

memo(Component, arePropsEqual?)

컴포넌트를 memo로 감싸서 해당 컴포넌트의 메모이즈된 버전을 얻을 수 있습니다. 부모 컴포넌트가 다시 렌더링되었을 때 해당 컴포넌트의 props가 변경되지 않았다면 일반적으로 해당 컴포넌트는 다시 렌더링되지 않을 것입니다. 그러나 React는 여전히 재렌더링을 수행할 수 있습니다. 메모이제이션은 성능 최적화를 위한 것이지, 보장되는 동작은 아닙니다.

import { memo } from 'react';

const SomeComponent = memo(function SomeComponent(props) {
  // ...
});

Parameters

  • Component: 메모이제이션을 원하는 컴포넌트입니다. memo는 이 컴포넌트를 수정하지 않고, 대신 새로운 메모이즈된 컴포넌트를 반환합니다. 함수 및 forwardRef 컴포넌트를 포함한 모든 유효한 React 컴포넌트가 허용됩니다.
  • optional arePropsEqual: 이 함수는 두 개의 인수, 즉 컴포넌트의 이전 props와 새로운 props를 받는 함수입니다. 이전 props와 새로운 props가 동일하면(새로운 props로도 컴포넌트가 동일한 출력을 생성하고 동일한 방식으로 동작한다면) true를 반환해야 합니다. 그렇지 않으면 false를 반환해야 합니다. 일반적으로 이 함수를 지정하지 않습니다. 기본적으로 React는 각 prop을 Object.is로 비교합니다.

Returns

memo는 새로운 React 컴포넌트를 반환합니다. memo에 제공된 컴포넌트와 동일하게 동작하지만, 부모 컴포넌트가 다시 렌더링되는 경우에는 props가 변경되지 않은 경우에는 React가 항상 다시 렌더링하지 않습니다.


Usage

Skipping re-rendering when props are unchanged

React는 일반적으로 부모 컴포넌트가 다시 렌더링될 때 해당 컴포넌트를 다시 렌더링합니다. 그러나 memo를 사용하면, 새로운 props가 이전 props와 동일한 경우 부모 컴포넌트가 다시 렌더링될 때 해당 컴포넌트를 React가 다시 렌더링하지 않도록 할 수 있습니다. 이러한 컴포넌트를 메모이즈된 컴포넌트라고 합니다.

컴포넌트를 메모이즈하려면 memo로 감싸고, 반환된 값을 원래의 컴포넌트 대신 사용하세요.

const Greeting = memo(function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
});

export default Greeting;

React 컴포넌트는 항상 순수한 렌더링 로직을 가져야 합니다. 즉, props, state, context가 변경되지 않은 경우 동일한 출력을 반환해야 합니다. memo를 사용함으로써 이 요구 사항을 준수한다는 것을 React에 알리기 때문에, props가 변경되지 않은 한 React는 다시 렌더링할 필요가 없습니다. 하지만memo를 사용하더라도 컴포넌트의 상태가 변경되거나 사용 중인 컨텍스트가 변경되면 다시 렌더링됩니다.

📝 Note
memo를 성능 최적화 용도로만 의존해야 합니다. memo 없이 코드가 작동하지 않는다면, 먼저 기저 문제를 찾고 해결해야 합니다. 그런 다음 성능을 개선하기 위해 memo를 추가할 수 있습니다.


Updating a memoized component using state

예를 들어, 컴포넌트가 메모이즈되어 있더라도 해당 컴포넌트의 상태가 변경되면 여전히 다시 렌더링됩니다. 메모이제이션은 부모로부터 컴포넌트에 전달되는 props와 관련이 있습니다.

만약 상태 변수를 현재 값으로 설정한다면, memo 없이도 React는 컴포넌트의 다시 렌더링을 건너뜁니다. 여전히 컴포넌트 함수가 추가로 호출되는 것을 볼 수 있지만, 결과는 버려집니다.


Updating a memoized component using a context

예를 들어, 컴포넌트가 메모이즈되어 있더라도 사용 중인 컨텍스트가 변경되면 여전히 다시 렌더링됩니다. 메모이제이션은 부모로부터 컴포넌트에 전달되는 props와 관련이 있습니다.

만약 컨텍스트의 일부가 변경될 때 컴포넌트를 다시 렌더링하려면, 컴포넌트를 두 개로 분할하세요. 외부 컴포넌트에서 컨텍스트에서 필요한 부분을 읽고, 이를 memoized된 자식 컴포넌트에 프롭으로 전달하세요.


Minimizing props changes

memo를 사용할 때는 어떤 프롭이 이전과 얕은 비교(shallow comparison)에서 같지 않을 때마다 컴포넌트가 다시 렌더링됩니다. 이는 React가 컴포넌트의 모든 프롭을 이전 값과 Object.is 비교를 통해 비교하기 때문입니다. Object.is(3, 3)true이지만, Object.is({}, {})false입니다.

memo를 최대한 활용하기 위해서는 프롭이 변경되는 횟수를 최소화해야 합니다. 예를 들어, 프롭이 객체인 경우, useMemo를 사용하여 부모 컴포넌트가 매번 해당 객체를 재생성하는 것을 방지할 수 있습니다.

function Page() {
  const [name, setName] = useState('Taylor');
  const [age, setAge] = useState(42);

  const person = useMemo(
    () => ({ name, age }),
    [name, age]
  );

  return <Profile person={person} />;
}

const Profile = memo(function Profile({ person }) {
  // ...
});

프롭 변경을 최소화하는 더 좋은 방법은 컴포넌트가 필요한 최소한의 정보만을 프롭으로 받을 수 있도록 하는 것입니다. 예를 들어, 전체 객체 대신 개별 값들을 받을 수 있도록 설정할 수 있습니다.

function Page() {
  const [name, setName] = useState('Taylor');
  const [age, setAge] = useState(42);
  return <Profile name={name} age={age} />;
}

const Profile = memo(function Profile({ name, age }) {
  // ...
});

심지어 개별 값들도 종종 덜 자주 변경되는 값으로 변환될 수 있습니다. 예를 들어, 여기에서 컴포넌트는 값 자체가 아닌 값의 존재 여부를 나타내는 boolean 값을 받습니다.

function GroupsLanding({ person }) {
  const hasGroups = person.groups !== null;
  return <CallToAction hasGroups={hasGroups} />;
}

const CallToAction = memo(function CallToAction({ hasGroups }) {
  // ...
});

메모이즈된 컴포넌트에 함수를 전달해야 할 때는, 해당 함수를 컴포넌트 외부에서 선언하여 변경되지 않도록 하거나, useCallback을 사용하여 재렌더링 사이에 함수 정의를 캐싱하는 방법을 사용하세요.


Specifying a custom comparison function

특수한 경우에는 메모이즈된 컴포넌트의 프롭 변경을 최소화하는 것이 불가능할 수 있습니다. 이 경우, 얕은 동등성(shallow equality) 대신 이전과 새로운 프롭을 비교하는 데 사용할 사용자 정의 비교 함수를 memo의 두 번째 인수로 제공할 수 있습니다. 이 함수는 true를 반환해야만 새로운 프롭이 이전 프롭과 동일한 출력을 만들어낼 경우이고, 그렇지 않은 경우에는 false를 반환해야 합니다.

const Chart = memo(function Chart({ dataPoints }) {
  // ...
}, arePropsEqual);

function arePropsEqual(oldProps, newProps) {
  return (
    oldProps.dataPoints.length === newProps.dataPoints.length &&
    oldProps.dataPoints.every((oldPoint, index) => {
      const newPoint = newProps.dataPoints[index];
      return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;
    })
  );
}

만약 이렇게 한다면, 컴포넌트를 다시 렌더링하는 것보다 비교 함수가 실제로 더 빠른지 확인하기 위해 브라우저 개발자 도구의 성능 패널을 사용하세요. 놀라울 수도 있습니다.

성능 측정을 할 때에는 React가 프로덕션 모드로 실행되고 있는지 확인하세요.

❗️ Pitfall


만약 사용자 정의 arePropsEqual 함수를 제공한다면, 함수를 포함한 모든 프롭을 비교해야 합니다. 함수는 종종 부모 컴포넌트의 프롭과 상태를 클로저로 갖습니다. 만약 oldProps.onClick !== newProps.onClick에서 true를 반환한다면, 컴포넌트의 onClick 핸들러 내에서 이전 렌더링에서의 프롭과 상태를 계속해서 참조하게 되어 매우 혼동스러운 버그가 발생할 수 있습니다.


arePropsEqual에서 깊은 동등성 체크를 수행하는 것은 깊이가 제한된 알려진 데이터 구조를 다루고 있을 때만 100% 확신할 경우에만 피하는 것이 좋습니다. 깊은 동등성 체크는 극도로 느려질 수 있으며, 데이터 구조가 나중에 변경되면 앱이 몇 초 동안 멈추는 문제가 발생할 수 있습니다.


출처: https://react.dev/reference/react/memo

profile
지치지 않는 백엔드 개발자 김성주입니다 :)

0개의 댓글