Search

useMemo

useMemo() 리액트 Hook은 useCallback()과 함께 리액트 앱의 최적화를 위해 사용합니다. 리액트 컴포넌트는 props나 상태(state)가 변경될 때마다 매번 리랜더링을 수행하고 변경된 값을 화면에 반영합니다. 리랜더링하는 과정에서 이전에 계산했던 값들을 모두 다시 계산하게 되는데, 이 때 계산 결과가 같기 때문에 굳이 재 계산하지 않아도 되는 경우가 생길 수 있겠죠.
이런 상황이라면 useMemo()를 사용해 컴포넌트가 불필요한 재 계산 작업을 수행하지 않고 기존 계산된 값을 캐시(cache)해 뒀다가 사용하는 메모이제이션(memoization) 처리를 할 수 있습니다. 메모이제이션된 값은 특정한 경우가 아니라면 재 계산을 수행하지 않고 캐시된 값을 바로 사용하기 때문에 불필요한 연산 수행을 방지합니다.
useMemo()를 사용해서 메모이제이션하는 것도 컴퓨팅 비용이 드는 작업이므로 남용하게 되면 오히려 앱 성능이 저하됩니다. 랜더링 과정에서 메모이제이션 비용보다 더 높은 고비용의 연산을 수행하는 값이 있을 경우에만 적절하게 사용해야 합니다.

useMemo 사용

useMemo()는 아래 형태로 사용합니다.
const expensiveValue = useMemo(() => { /* 메모이제이션할 결과 값을 리턴하는 복잡한 작업 */ }, [/* 의존성 배열 */]);
JavaScript
복사
첫번째 인자는 메모이제이션할 값을 리턴하는 함수입니다. 두번째 인자는 useEffect()에서 보았던 것과 같은 의존성 배열(dependency array)입니다. useMemo()의 의존성 배열도 useEffect()의 의존성 배열과 같은 기능을 합니다. 의존성 배열의 조건에 따라 메모이제이션한 값을 다시 계산하게 될지 여부가 결정됩니다.
예를 들어 아래 코드처럼 의존성 배열이 [a, b]처럼 되어있는 경우, 컴포넌트가 마운트(mount)된 이후 a 또는 b의 값이 변경된 경우에만 첫번째 인자의 함수를 다시 실행해서 새로 값을 메모이제이션합니다.
const App = ({ a, b, c }) => { const expensiveValue = useMemo(() => { // a를 b제곱하는 계산을 백만번 수행하는 복잡한 연산식 😱 return Array.from( { length: 1000000 }, () => a ** b ).join(' '); }, [a, b]); // 의존성 배열에 a와 b만 추가했으므로 c가 변경되더라도 재 계산을 수행하지 않음 return ( <div> <h1>{c}</h1> <p>{expensiveValue}</p> </div> ); };
JavaScript
복사
의존성 배열(dependency array)useEffect()에 대해 잘 모르신다면 useEffect 편을 확인하세요.

컴포넌트를 메모이제이션하기

컴포넌트가 리랜더링하게 되면, 포함하고 있는 하위 컴포넌트들도 함께 리랜더링 됩니다. 이 때 하위 컴포넌트는 변경되는 점이 없어 리랜더링 된 결과가 이전과 같을 수 있습니다. 이런 경우 하위 컴포넌트는 불필요하게 랜더링을 다시 수행하므로 비효율적입니다.
예를 들어 아래 코드처럼 상위 컴포넌트인 <Parent />에서 countcolor 두개의 상태를 사용하고 있고 color 상태만 props로 받아 사용하는 하위 컴포넌트인 <Child />가 있을 때, count가 변경될 때에도 불필요하게 <Child />가 리랜더링 되는 것을 확인할 수 있습니다.
import ReactDOM from 'react-dom/client'; import { useState, useEffect, useRef } from 'react'; // Child 하위 컴포넌트 const Child = ({ color }) => { const renderCount = useRef(0); // 랜더링할 때마다 콘솔에 랜더링 횟수를 로그로 남김 useEffect(() => { renderCount.current = renderCount.current + 1; console.log(`🤹🏻‍♂️ Child 컴포넌트 ${renderCount.current}번 랜더링`); }); return <h2 style={{ color }}>{color}</h2>; }; // Parent 상위 컴포넌트 const Parent = () => { const [count, setCount] = useState(0); const [color, setColor] = useState('blue'); return ( <> <div> <h1>카운트: {count}</h1> <button onClick={() => setCount((c) => c + 1)}> + </button> </div> <div> <Child color={color} /> <button onClick={() => setColor('red')}> red로 변경 </button> <button onClick={() => setColor('blue')}> blue로 변경 </button> </div> </> ); }; const rootNode = document.getElementById('root'); ReactDOM.createRoot(rootNode).render(<Parent />);
JavaScript
복사
+ 버튼을 눌렀을 때 count 상태가 변경되므로 color 상태만 프로퍼티로 받아 사용하는 Child 컴포넌트는 바뀌는 점이 없지만 리랜더링되는 것을 볼 수 있습니다.
이 때 <Parent />에서 useMemo()로 하위 컴포넌트인 <Child />를 메모이제이션해서 사용하면 이런 불필요한 컴포넌트의 리랜더링을 방지할 수 있습니다. <Child />를 메모이제이션하고 의존성 배열에 color만 포함시킨 뒤 확인해보면 color 상태가 변경될 때만 <Child />의 리랜더링이 발생하는 것을 확인할 수 있습니다.
import ReactDOM from 'react-dom/client'; import { useState, useEffect, useMemo, useRef } from 'react'; // Child 하위 컴포넌트 const Child = ({ color }) => { const renderCount = useRef(0); // 랜더링할 때마다 콘솔에 랜더링 횟수를 로그로 남김 useEffect(() => { renderCount.current = renderCount.current + 1; console.log(`🤹🏻‍♂️ Child 컴포넌트 ${renderCount.current}번 랜더링`); }); return <h2 style={{ color }}>{color}</h2>; }; // Parent 상위 컴포넌트 const Parent = () => { const [count, setCount] = useState(0); const [color, setColor] = useState('blue'); // useMemo를 사용해서 Child 컴포넌트를 메모이제이션하여 사용 // 의존성 배열에는 Child가 사용하는 color 상태만 포함 const memoizedChild = useMemo(() => { return <Child color={color} />; }, [color]); return ( <> <div> <h1>카운트: {count}</h1> <button onClick={() => setCount((c) => c + 1)}> + </button> </div> <div> {/* 기존 Child 컴포넌트를 메모이제이션된 것으로 교체 */} {memoizedChild} <button onClick={() => setColor('red')}> red로 변경 </button> <button onClick={() => setColor('blue')}> blue로 변경 </button> </div> </> ); }; const rootNode = document.getElementById('root'); ReactDOM.createRoot(rootNode).render(<Parent />);
JavaScript
복사
메모이제이션된 Child 컴포넌트는 의존성 배열에 없는 값인 count가 상위 컴포넌트에서 변경되더라도 리랜더링 되지 않습니다.
컴포넌트를 별도로 선언해서 메모이제이션하지 않고 JSX 구문 내에서 직접 useMemo()를 사용해서 메모이제이션해도 됩니다.
... return ( <> <div> <h1>카운트: {count}</h1> <button onClick={() => setCount((c) => c + 1)}> + </button> </div> <div> {/* JSX 구문내에서 바로 useMemo 사용 */} {useMemo(() => { return <Child color={color} />; }, [color])} <button onClick={() => setColor('red')}> red로 변경 </button> <button onClick={() => setColor('blue')}> blue로 변경 </button> </div> </> ); ...
JavaScript
복사
JSX 구문 내에서 직접 useMemo()를 사용하면 메모이제이션한 컴포넌트를 다른 요소와 함께 확인할 수 있는 장점이 있습니다.

JSX 요소를 메모이제이션

컴포넌트를 메모이제이션할때와 마찬가지로 일부 JSX 요소만 useMemo()를 적용하는 것도 물론 가능합니다. 아래는 기존 코드에서 <Child /> 컴포넌트를 삭제하고 기존 JSX 구문을 가져와 <App />의 원래 자리로 대체하고 해당 JSX 요소 부분만 useMemo()를 적용해본 코드입니다. 확인해보면 useMemo()가 적용된 <h2> 요소는 의존성 배열로 지정한 color가 변경되지 않으면 다시 생성되지 않습니다.
import ReactDOM from 'react-dom/client'; import { useState, useMemo } from 'react'; const Parent = () => { const [count, setCount] = useState(0); const [color, setColor] = useState('blue'); return ( <> <div> <h1>카운트: {count}</h1> <button onClick={() => setCount((c) => c + 1)}> + </button> </div> <div> {/* 요소 중 일부만 useMemo로 메모이제이션 적용 */} {useMemo(() => { console.log('🤹🏻‍♂️ h2 요소 랜더링'); return <h2 style={{ color }}>{color}</h2>; }, [color])} <button onClick={() => setColor('red')}> red로 변경 </button> <button onClick={() => setColor('blue')}> blue로 변경 </button> </div> </> ); }; const rootNode = document.getElementById('root'); ReactDOM.createRoot(rootNode).render(<Parent />);
JavaScript
복사
useMemo로 메모이제이션한 <h2>요소는 color 상태가 변경될때만 다시 호출하여 생성되는 것을 확인할 수 있습니다.

 useRef

 useCallback

React Hooks 목록