---
title: memo
---
`memo`를 사용하면 컴포넌트의 Props가 변경되지 않은 경우 리렌더링을 건너뛸 수 있습니다.
```
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
```
[React 컴파일러](/learn/react-compiler)는 모든 컴포넌트에 `memo`와 동일한 최적화를 자동으로 적용하므로 수동으로 메모이제이션을 할 필요가 줄어듭니다. 컴파일러를 사용해 컴포넌트 메모이제이션을 자동으로 처리할 수 있습니다.
---
## 레퍼런스 {/*reference*/}
### `memo(Component, arePropsEqual?)` {/*memo*/}
컴포넌트를 `memo`로 감싸면 해당 컴포넌트의 메모된Memoized 버전을 얻을 수 있습니다. 메모된 버전의 컴포넌트는 일반적으로 부모 컴포넌트가 리렌더링 되어도 Props가 변경되지 않았다면 리렌더링되지 않습니다. 그러나 메모이제이션은 성능을 최적화하는 것이지, 보장하는 것은 아니기 때문에 React는 여전히 다시 렌더링될 수도 있습니다.
```js
import { memo } from 'react';
const SomeComponent = memo(function SomeComponent(props) {
// ...
});
```
[아래 예시를 참고하세요.](#usage)
#### 매개변수 {/*parameters*/}
* `Component`: 메모Memoize하려는 컴포넌트입니다. `memo`는 이 컴포넌트를 수정하지 않고 대신 새로운 메모된 컴포넌트를 반환합니다. 함수와 [`forwardRef`](/reference/react/forwardRef) 컴포넌트를 포함한 모든 유효한 React 컴포넌트가 허용됩니다.
* `arePropsEqual`**(선택사항)**: 컴포넌트의 이전 Props와 새로운 Props의 두 가지 인수를 받는 함수입니다. 이전 Props와 새로운 Props가 동일한 경우, 컴포넌트가 이전 Props와 동일한 결과를 렌더링하고 새로운 Props에서도 이전 Props와 동일한 방식으로 동작하는 경우 `true`를 반환해야 합니다. 그렇지 않으면 `false`를 반환해야 합니다. 일반적으로 이 함수를 지정하지 않습니다. React는 기본적으로 [`Object.is`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is)로 각 Props를 비교합니다.
#### 반환값 {/*returns*/}
`memo`는 새로운 React 컴포넌트를 반환합니다. `memo`에 제공한 컴포넌트와 동일하게 동작하지만, 부모가 리렌더링되더라도 Props가 변경되지 않는 한 React는 이를 리렌더링하지 않습니다.
---
## 사용법 {/*usage*/}
### Props가 변경되지 않았을 때 리렌더링 건너뛰기 {/*skipping-re-rendering-when-props-are-unchanged*/}
React는 일반적으로 부모가 리렌더링될 때마다 컴포넌트를 리렌더링합니다. `memo`를 사용하면, 새로운 Props가 이전 Props와 같으면 부모 컴포넌트가 다시 렌더링되더라도 React가 해당 컴포넌트를 다시 렌더링하지 않도록 만들 수 있습니다. 이러한 컴포넌트를 메모된Memoized 상태라고 합니다.
컴포넌트를 메모하려면 `memo`로 감싸고 기존 컴포넌트 대신에 반환된 값을 사용하세요.
```js
const Greeting = memo(function Greeting({ name }) {
return
Hello, {name}!
;
});
export default Greeting;
```
React 컴포넌트는 항상 [순수한 렌더링 로직](/learn/keeping-components-pure)을 가져야 합니다. 이는 Props, State 그리고 Context가 변경되지 않으면 항상 동일한 결과를 반환해야 함을 의미합니다. `memo`를 사용하면 컴포넌트가 이 요구 사항을 준수한다고 알리므로, Props가 변경되지 않는 한 React는 리렌더링 될 필요가 없습니다. `memo`를 사용하더라도 컴포넌트의 State가 변경되거나 사용 중인 Context가 변경되면 리렌더링 됩니다.
아래 예시에서 `Greeting` 컴포넌트는 `name`이 Props 중 하나이기 때문에 `name`이 변경될 때마다 리렌더링 됩니다. 하지만 `address`는 `Greeting`의 Props가 아니기 때문에 `address`가 변경될 때는 리렌더링되지 않습니다.
```js
import { memo, useState } from 'react';
export default function MyApp() {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
return (
<>
>
);
}
const Greeting = memo(function Greeting({ name }) {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
return
Hello{name && ', '}{name}!
;
});
```
```css
label {
display: block;
margin-bottom: 16px;
}
```
**`memo`는 성능 최적화를 위해서 사용해야 합니다.** `memo` 없이 코드가 작동하지 않는다면, 먼저 근본적인 문제를 찾아서 해결하세요. 이후에 `memo`를 추가하여 성능을 개선할 수 있습니다.
#### 모든 곳에 `memo`를 추가해야할까요? {/*should-you-add-memo-everywhere*/}
이 사이트처럼 앱에서 대부분의 상호작용이 (페이지나 섹션 전체를 교체하는 것처럼) 큰 단위로 이루어진다면, 메모이제이션은 대개 필요하지 않습니다. 반면에 그림 편집기처럼 대부분의 상호작용이 (도형을 움직이는 것처럼) 세밀한 단위로 이루어진다면, 메모이제이션이 매우 유용할 수 있습니다.
`memo`로 최적화하는 것은 컴포넌트가 정확히 동일한 Props로 자주 리렌더링 되고, 리렌더링 로직이 비용이 많이 드는 경우에만 유용합니다. 컴포넌트가 리렌더링 될 때 인지할 수 있을 만큼의 지연이 없다면 `memo`가 필요하지 않습니다. `memo`는 객체 또는 렌더링 중에 정의된 일반 함수처럼 *항상 다른* Props가 컴포넌트에 전달되는 경우에 완전히 무용지물입니다. 따라서 `memo`와 함께 [`useMemo`](/reference/react/useMemo#skipping-re-rendering-of-components)와 [`useCallback`](/reference/react/useCallback#skipping-re-rendering-of-components)이 종종 필요합니다.
그 외의 경우에는 컴포넌트를 `memo`로 감싸는 이점이 없습니다. 그렇다고 해서 크게 해가 되지 않기 때문에 일부 팀에서는 개별 사례에 대해 고려하지 않고 가능한 한 많이 메모이제이션하는 방식을 선택하기도 합니다. 이 접근 방식은 코드 가독성이 떨어진다는 단점이 있습니다. 또한 모든 메모이제이션이 효과적이지는 않습니다. 항상 변경되는 값이 하나라도 있다면, 컴포넌트 전체의 메모이제이션을 중단하기에 충분합니다.
**실제로 몇가지 원칙을 따르면 메모이제이션이 불필요할 수 있습니다.**
1. 컴포넌트가 다른 컴포넌트를 시각적으로 감쌀 때 [JSX를 자식으로 받아들이도록 하세요.](/learn/passing-props-to-a-component#passing-jsx-as-children) 이렇게 하면 래퍼 컴포넌트가 자신의 State를 업데이트할 때 React는 그 자식 컴포넌트가 리렌더링 될 필요가 없다는 것을 알 수 있습니다.
2. 지역 State를 선호하고 필요 이상으로 [State 끌어올리기](/learn/sharing-state-between-components)를 하지 마세요. 예를 들어, 최상위 트리나 전역 State 라이브러리에 폼이나 아이템이 호버Hover되었는지와 같은 일시적인 State를 두지 마세요.
3. [렌더링 로직을 순수하게](/learn/keeping-components-pure) 유지하세요. 컴포넌트를 렌더링했을 때 문제가 발생하거나 눈에 띄는 시각적 아티팩트가 생성된다면 컴포넌트에 버그가 있는 것입니다! 메모이제이션하는 대신 버그를 수정하세요.
4. [State를 업데이트하는 불필요한 Effect](/learn/you-might-not-need-an-effect)를 피하세요. React 앱에서 대부분의 성능 문제는 컴포넌트를 반복해서 렌더링하게 만드는 Effect에서 발생하는 일련의 업데이트로 인해 발생합니다.
5. [Effect에서 불필요한 의존성을 제거](/learn/removing-effect-dependencies)하세요. 예를 들어, 메모이제이션 대신에 일부 객체나 함수를 Effect 내부나 컴포넌트 외부로 이동하는 것이 더 간단할 때가 많습니다.
특정 상호작용이 여전히 느리게 느껴진다면 [React 개발자 도구 Profiler](https://legacy.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html)를 사용해 어떤 컴포넌트가 메모이제이션을 통해 가장 큰 이점을 얻을 수 있는지 확인하고 필요한 경우에 메모이제이션하세요. 이러한 원칙은 컴포넌트를 더 쉽게 디버깅하고 이해할 수 있게 해주므로 어떤 경우든 이 원칙을 따르는 것이 좋습니다. 장기적으로는 이 문제를 완전히 해결하기 위해 [세분된 메모이제이션을 자동으로 수행하는 방법](https://www.youtube.com/watch?v=lGEMwh32soc)을 연구하고 있습니다.
---
### State를 사용해 메모이제이션된 컴포넌트 업데이트하기 {/*updating-a-memoized-component-using-state*/}
컴포넌트가 메모이제이션된 경우에도, 컴포넌트의 State가 변경되면 리렌더링됩니다. 메모이제이션은 부모에서 컴포넌트로 전달되는 Props에만 적용됩니다.
```js
import { memo, useState } from 'react';
export default function MyApp() {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
return (
<>
>
);
}
const Greeting = memo(function Greeting({ name }) {
console.log('Greeting was rendered at', new Date().toLocaleTimeString());
const [greeting, setGreeting] = useState('Hello');
return (
<>
{greeting}{name && ', '}{name}!
>
);
});
function GreetingSelector({ value, onChange }) {
return (
<>
>
);
}
```
```css
label {
display: block;
margin-bottom: 16px;
}
```
State 변수를 현재 값으로 설정하면 React는 `memo` 없이도 컴포넌트 리렌더링을 건너뜁니다. 컴포넌트가 한 번 더 호출될 수 있지만, 결과는 무시됩니다.
---
### Context를 사용하여 메모화된 컴포넌트 업데이트하기 {/*updating-a-memoized-component-using-a-context*/}
컴포넌트가 메모되었더라도, 사용 중인 Context가 변경될 때 컴포넌트는 리렌더링됩니다. 메모는 부모로부터 전달되는 Props에만 적용됩니다.
```js
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 (
);
}
const Greeting = memo(function Greeting({ name }) {
console.log("Greeting was rendered at", new Date().toLocaleTimeString());
const theme = useContext(ThemeContext);
return (
Hello, {name}!
);
});
```
```css
label {
display: block;
margin-bottom: 16px;
}
.light {
color: black;
background-color: white;
}
.dark {
color: white;
background-color: black;
}
```
일부 Context의 일정 부분이 변경될 때만 컴포넌트가 리렌더링되도록 하려면 컴포넌트를 두 개로 나눠야 합니다. 외부 컴포넌트의 Context에서 필요한 내용을 읽고, 메모화된 자식에게 Prop으로 전달하세요.
---
### Props 변경 최소화하기 {/*minimizing-props-changes*/}
`memo`를 사용할 때 어떤 Prop든 이전의 Prop과 *얕은 비교 결과*가 같지 않을 때마다 컴포넌트가 리렌더링 됩니다. 즉 React는 [`Object.is`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is) 비교를 사용하여 컴포넌트의 모든 Prop을 이전 값과 비교합니다. `Object.is(3, 3)`는 `true`이지만 `Object.is({}, {})`는 `false`입니다.
`memo`를 최대한 활용하려면, Props가 변경되는 횟수를 최소화해야 합니다. 예를 들어 Prop이 객체인 경우, [`useMemo`](/reference/react/useMemo)를 사용하여 부모 컴포넌트가 해당 객체를 매번 다시 만드는 것을 방지하세요.
```js {5-8}
function Page() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
const person = useMemo(
() => ({ name, age }),
[name, age]
);
return ;
}
const Profile = memo(function Profile({ person }) {
// ...
});
```
Props의 변경을 최소화하는 더 좋은 방법은 컴포넌트가 Props에 필요한 최소한의 정보만 받도록 하는 것입니다. 예를 들어, 전체 객체 대신 개별 값을 받을 수 있습니다.
```js {4,7}
function Page() {
const [name, setName] = useState('Taylor');
const [age, setAge] = useState(42);
return ;
}
const Profile = memo(function Profile({ name, age }) {
// ...
});
```
때로는 개별 값도 자주 변경되지 않는 값으로 사용할 수 있습니다. 예를 들어 다음 컴포넌트는 값 자체가 아니라 값의 존재를 나타내는 불리언 값을 받습니다.
```js {3}
function GroupsLanding({ person }) {
const hasGroups = person.groups !== null;
return ;
}
const CallToAction = memo(function CallToAction({ hasGroups }) {
// ...
});
```
메모화된 컴포넌트에 함수를 전달해야 하는 경우, 컴포넌트 외부에 함수를 선언하여 변경되지 않도록 하거나, [`useCallback`](/reference/react/useCallback#skipping-re-rendering-of-components)을 사용하여 리렌더링 사이에 함수의 선언을 캐시합니다.
---
### 사용자 정의 비교 함수 지정하기 {/*specifying-a-custom-comparison-function*/}
드물지만 메모화된 컴포넌트의 Props 변경을 최소화하는 것이 불가능할 수 있습니다. 이 경우 사용자 정의 비교 함수를 제공하여 React가 얕은 비교를 사용하는 대신에 이전 Props와 새로운 Props를 비교할 수 있습니다. 이 함수는 `memo`의 두 번째 인수로 전달됩니다. 새로운 Props가 이전 Props와 동일한 결과를 생성하는 경우에만 `true`를 반환해야 합니다. 그렇지 않으면 `false`를 반환해야 합니다.
```js {3}
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가 프로덕션 환경에서 실행되고 있는지 확인하세요.
`arePropsEqual`를 구현하는 경우 **함수를 포함하여 모든 Prop를 비교해야 합니다.** 함수는 종종 부모 컴포넌트의 Props와 State를 [클로저Closure로 다룹니다](https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures). `oldProps.onClick !== newProps.onClick`일 때 `true`를 반환하면 컴포넌트가 `onClick` 핸들러 내에서 이전 렌더링의 Props와 State를 계속 "인식"하여 매우 혼란스러운 버그가 발생할 수 있습니다.
작업 중인 데이터 구조가 알려진 제한된 깊이를 가지고 있다고 100% 확신하지 않는 한, `arePropsEqual` 내에서 깊은 비교를 수행하지 마세요. **깊은 비교는 매우 느려질 수 있으며** 나중에 누군가 데이터 구조를 변경하면 앱이 잠깐 정지될 수 있습니다.
---
### React 컴파일러를 사용한다면 React.memo가 여전히 필요한가요? {/*react-compiler-memo*/}
[React 컴파일러](/learn/react-compiler)를 활성화하면 일반적으로 `React.memo`가 더 이상 필요하지 않습니다. 컴파일러가 컴포넌트 리렌더링을 자동으로 최적화해 주기 때문입니다.
작동 방식은 다음과 같습니다.
**React 컴파일러를 활성화하지 않으면** 불필요한 리렌더링을 막기 위해 `React.memo`가 필요합니다.
```js
// 부모가 매초 리렌더링됩니다
function Parent() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<>
Seconds: {seconds}
>
);
}
// memo가 없으면 Props가 변경되지 않아도 매초 리렌더링됩니다
const ExpensiveChild = memo(function ExpensiveChild({ name }) {
console.log('ExpensiveChild rendered');
return
Hello, {name}!
;
});
```
**React 컴파일러를 활성화하면** 동일한 최적화가 자동으로 이루어집니다.
```js
// memo가 필요 없습니다. 컴파일러가 리렌더링을 자동으로 막아 줍니다
function ExpensiveChild({ name }) {
console.log('ExpensiveChild rendered');
return
Hello, {name}!
;
}
```
React 컴파일러가 생성하는 코드의 핵심 부분은 다음과 같습니다.
```js {6-12}
function Parent() {
const $ = _c(7);
const [seconds, setSeconds] = useState(0);
// ... 그 외 코드 ...
let t3;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t3 = ;
$[4] = t3;
} else {
t3 = $[4];
}
// ... return 문 ...
}
```
강조된 줄을 살펴보세요. 컴파일러는 ``를 캐시 검사로 감쌉니다. `name` Prop이 항상 `"John"`이므로, 이 JSX는 한 번만 생성되고 부모가 리렌더링될 때마다 재사용됩니다. 이는 정확히 `React.memo`가 하는 일과 같습니다. 즉, 자식의 Props가 변경되지 않았을 때 자식이 리렌더링되지 않도록 막아 줍니다.
React 컴파일러는 자동으로 다음을 수행합니다.
1. `ExpensiveChild`에 전달된 `name` Prop이 변경되지 않았음을 추적합니다.
2. 이전에 생성한 `` JSX를 재사용합니다.
3. `ExpensiveChild`의 리렌더링을 완전히 건너뜁니다.
즉, **React 컴파일러를 사용할 때는 컴포넌트에서 `React.memo`를 안전하게 제거할 수 있습니다.** 컴파일러가 동일한 최적화를 자동으로 제공하므로 코드가 더 깔끔해지고 유지보수하기 쉬워집니다.
컴파일러의 최적화는 사실 `React.memo`보다 더 포괄적입니다. 컴포넌트 내부의 중간값과 비용이 많이 드는 계산까지 메모이제이션하는데, 이는 컴포넌트 트리 전반에 걸쳐 `React.memo`와 `useMemo`를 함께 사용하는 것과 비슷합니다.
---
## 문제 해결 {/*troubleshooting*/}
### Prop이 객체, 배열 또는 함수일 때 컴포넌트가 리렌더링됩니다 {/*my-component-rerenders-when-a-prop-is-an-object-or-array*/}
React는 얕은 비교를 기준으로 이전 Props와 새로운 Props를 비교합니다. 즉, 각각의 새로운 Prop가 이전 Prop와 참조가 동일한지 여부를 고려합니다. 부모가 리렌더링 될 때마다 새로운 객체나 배열을 생성하면, 개별 요소들이 모두 동일하더라도 React는 여전히 변경된 것으로 간주합니다. 마찬가지로 부모 컴포넌트를 렌더링할 때 새로운 함수를 만들면 React는 함수의 정의가 동일하더라도 변경된 것으로 간주합니다. 이를 방지하려면 [부모 컴포넌트에서 Props를 단순화하거나 메모화 하세요.](#minimizing-props-changes)