컴포넌트의 props가 변하거나 컴포넌트 내의 state가 변하면 컴포넌트는 리렌더링됩니다.
React에서 메모이제이션(Memoization)은 불필요한 렌더링을 방지하고 애플리케이션 성능을 최적화하기 위한 방법입니다.
React 컴포넌트와 함수 호출의 결과를 저장하여 동일한 입력에 대해 반복 계산을 줄이는 데 사용됩니다.
React Hook 은 기본적으로 컴포넌트가 마운트될 때 한 번 생성됩니다.
컴포넌트가 리렌더링되더라도 setState 함수는 동일한 참조를 유지합니다.
이는 리액트가 내부적으로 setState를 메모이제이션하기 때문에 가능하며, 성능을 최적화하기 위한 설계입니다.
컴포넌트를 메모이제이션합니다.
부모 컴포넌트가 리렌더링될 때 자식 컴포넌트가 불필요하게 리렌더링되지 않도록 합니다.
function App() {
return (
<>
<CompoA />
</>
);
}
export default App;
------CompoA
import { useState } from "react";
import CompoB from "./CompoB";
export default function CompoA() {
const [count, setCount] = useState(0);
console.log("A rendering");
return (
<>
<h1>A</h1>
<div>
<div>{count}</div>
<button onClick={() => setCount((prev) => prev + 1)}>A증가</button>
</div>
<CompoB />
</>
);
}
--------CompoB
import CompoC from "./CompoC";
export default function CompoB() {
return (
<>
<h1>B</h1>
<CompoC />
</>
);
}
-------CompoC
export default function CompoC() {
return (
<>
<h1>C</h1>
</>
);
}
위와 같은 코드에서 컴포넌트A에 있는 증가 버튼을 누르면 컴포넌트A에 있는 state가 변하게 되고 A가 리렌더링이 되면서 B와 C로 같이 리렌더링이 됩니다.

이는 불필요한 리렌더링 입니다. React.memo를 이용하여 컴포넌트의 불필요한 리렌더링을 막을 수 있습니다.
//CompoB
import React from "react";
import CompoC from "./CompoC";
export default React.memo(function CompoB() {
console.log("B rendering");
return (
<>
<h1>B</h1>
<CompoC />
</>
);
});

컴포넌트A가 리렌더링 되더라도 컴포넌트B는 리렌더링 되지 않으므로 B rendering, C rendering은 출력되지 않습니다.
useCallback은 함수의 재생성을 방지하여 함수의 참조가 동일하게 유지되도록 함으로써 컴포넌트가 불필요하게 리렌더링되는 것을 막습니다.
----CompoA
import { useState } from "react";
import CompoB from "./CompoB";
export default function CompoA() {
const [count, setCount] = useState(0);
const increment = () => {
setCount((prev) => prev + 1);
};
console.log("A rendering");
return (
<>
<h1>A</h1>
<div>
<div>{count}</div>
<button onClick={() => setCount((prev) => prev + 1)}>A증가</button>
</div>
<CompoB increment={increment} />
</>
);
}
----CompoB
import React from "react";
import CompoC from "./CompoC";
export default React.memo(function CompoB({increment}:{ increment: () => void; }) {
console.log("B rendering");
return (
<>
<h1>B</h1>
<CompoC />
</>
);
});
위 코드와 같이 increment가 컴포넌트B에 전달되는 상황에서 버튼을 클릭하면 컴포넌트A가 리렌더링 되고,
increment는 재생성되어 컴포넌트B로 전달됩니다.
컴포넌트B는 React.memo로 메모이제이션 되었지만, props인 increment가 바뀌었으니 리렌더링됩니다.
컴포넌트A를 다음과 같이 수정하면 increment의 불필요한 재생성을 막을 수 있습니다.
import { useCallback, useState } from "react";
import CompoB from "./CompoB";
export default function CompoA() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
console.log("A rendering");
return (
<>
<h1>A</h1>
<div>
<div>{count}</div>
<button onClick={() => setCount((prev) => prev + 1)}>A증가</button>
</div>
<CompoB increment={increment} />
</>
);
}
useMemo는 React에서 값을 메모이제이션하여 불필요한 계산을 방지합니다.
주로 비용이 많이 드는 계산을 최적화할 때 사용합니다.
다음과 같이 연산비용이 높은 코드를 추가해보겠습니다.
//utils
export const initialItems = new Array(29_999_999).fill(0).map((_, i) => {
return {
id: i,
selected: i === 29_999_998,
};
});
//App.tsx
import CompoA from "./components/CompoA";
import { initialItems } from "./utils/utils";
function App() {
const selectedItem = initialItems.find((item) => item.selected);
return (
<>
<p>{selectedItem?.id}</p>
<CompoA />
</>
);
}
export default App;
연산비용이 높은 값을 구하기 때문에 처음 렌더링될 때도 오래걸리고, 리렌더링될 때도 딜레이가 생깁니다.
다음과 같이 useMemo를 사용하면 계산 값을 메모이제이션 하여 계산을 한 번만 수행하고,
이후에는 의존성 값이 변하지 않으면 계산을 생략합니다.
//App.tsx
import CompoA from "./components/CompoA";
import { initialItems } from "./utils/utils";
import { useMemo } from "react";
function App() {
const selectedItem = useMemo(
() => initialItems.find((item) => item.selected),
[]
);
return (
<>
<p>{selectedItem?.id}</p>
<CompoA />
</>
);
}
export default App;