본문 바로가기
프로그래밍/React

[React] memo

by YuminK 2023. 11. 29.

memo

memo lets you skip re-rendering a component when its props are unchanged.

 

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

 

Parameters 

Component: The component that you want to memoize. The memo does not modify this component, but returns a new, memoized component instead. Any valid React component, including functions and forwardRef components, is accepted.

 

optional arePropsEqual: A function that accepts two arguments: the component’s previous props, and its new props. It should return true if the old and new props are equal: that is, if the component will render the same output and behave in the same way with the new props as with the old. Otherwise it should return false. Usually, you will not specify this function. By default, React will compare each prop with Object.is.

 

Returns 

memo returns a new React component. It behaves the same as the component provided to memo except that React will not always re-render it when its parent is being re-rendered unless its props have changed.

 

부모 컴포넌트가 다시 그려질 때, props의 값이 변하지 않은 경우 다시 렌더링하지 않는다. 그러나 다시 그려지지 않음을 보장하진 않는다.

리액트 컴포넌트는 항상 pure 렌더링 로직을 가져야 한다. (should) 만약 props, state, context가 변하지 않았다면 항상 같은 결과를 내야 한다는 것이다.

 

memo를 사용하는 것은 리액트에게 해당 컴포넌트가 이러한 요구사항을 준수한다는 것을 의미한다. 따라서 리액트는 내부 상태, 값이 변하지 않으면 렌더링을 할 필요가 없다. 오직 내부에서 사용하는 상태, 컨텍스트 정보가 변경이 되었을 때만 다시 렌더링할 것이다.

 

동일한 props를 사용하면서 자주 렌더링 되는 컴포넌트가 있으며, 해당 로직이 비싼 경우 memo하는 의미가 있다. 딱히 느껴지는 렉이 없다면 필수적이진 않다.  만약 들어가는 props가 매번 변경이 된다면(object, plain function 같은) memo는 쓸모가 없다. 

 

useContext를 사용하고 있는 경우 props의 값이 변하지 않더라도 다시 렌더링된다. 

 

import { createContext, memo, useContext, useState } from 'react';

 

const ThemeContext = createContext(null);

 

export default function MyApp() {

  const [theme, setTheme] = useState('dark');

 

  function handleClick() {

    setTheme(theme === 'dark' ? 'light' : 'dark'); 

  }

 

  return (

    <ThemeContext.Provider value={theme}>

      <button onClick={handleClick}>

        Switch theme

      </button>

      <Greeting name="Taylor" />

    </ThemeContext.Provider>

  );

}

 

const Greeting = memo(function Greeting({ name }) {

  console.log("Greeting was rendered at", new Date().toLocaleTimeString());

  const theme = useContext(ThemeContext);

  return (

    <h3 className={theme}>Hello, {name}!</h3>

  );

});

 

몇 컨텍스트 부분이 변경될 때만 렌더링하도록 하려면, 컴포넌트를 둘로 나눈 이후에 외부 컴포넌트에서 상태를 읽고 하위 컴포넌트에게 props로 전달하라.

 

하위 요소에서 memo를 사용하고 있을 때, props로 오브젝트를 넘긴다면 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 }) {

  // ...

});

 

더 좋은 방법은 전체 오브젝트를 넘기는 대신에 필요한 최소한의 정보만 props로 넘기는 것이다.

 

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 }) {

  // ...

});

 

함수를 메모이제이션된 컴포넌트에 넘길 때, 함수를 컴포넌트 외부에 선언하여 변경되지 않도록 하거나 useCallback을 사용하여 캐싱하여 사용해라. 드문 경우지만, props의 변경을 최소화하는 것이 어려울 수 있다. 이런 경우 커스텀 비교 함수를 만들어서 넘겨줄 수 있는데, 리액트에서는 shallow equality에 대한 처리 대신 비교 함수를 사용하여 비교한다. 비교함수는 이전과 결과가 같다면 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;

    })

  );

}

 

만약 커스텀 arePropsEqual을 제공한다면, 함수를 포함하여 prop을 매번 비교해야 한다. 함수는 자주 부모 컴포넌트의 상태와 props를 닫는다. (close over) 만약 oldProps.onClick !== newProps.onClick가 만족할 때 true를 반환한다면, 컴포넌트는 이전 렌더에서 props의 onCilck 핸들러를 계속 지켜봐야 할 것이다. 이는 매우 혼란스러운 버그를 만들 수 있다. 

 

처리하고 있는 구조가 제한된 깊이로 처리된다는 것을 완전히 확신할 수 없다면, deep equality 확인을 피해라. 만약 누군가 구조를 변경한다면, Deep equality는 엄청나게 느릴 수 있고, 앱을 몇 초간 freeze할 가능성이 있다. memo를 잘 사용하려면 넘겨주는 props 값이 메모이제이션 되어 있어야 한다. 매번 생성되는 오브젝트, 함수 등을 넘겨서는 안 된다. 

 

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

'프로그래밍 > React' 카테고리의 다른 글

[React] useContext  (0) 2023.11.29
[React] forwardRef  (0) 2023.11.29
[React] useMemo  (0) 2023.11.29
[React] useCallback  (1) 2023.11.28
[React] useRef  (0) 2023.11.28

댓글