useMemo
useMemo는 리액트에서 컴포넌트의 성능을 최적화 하는데 사용되는 훅입니다.
useMemo에서 memo는 memoization을 뜻하는데 해석하자면 '메모리에 넣기' 라는 의미로
컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술입니다.
쉽게 말해서 , 동일한 값을 반환하는 함수를 반복적으로 호출해야 하는 경우
처음 값을 계산할 때 해당 값을 메모리에 저장해 필요할 때 마다 다시 계산하지 않고 메모리에서 꺼내 재사용하는 것입니다.
useMemo의 구조에 대해 간단하게 살펴봅시다
const value = useMemo(() => {
return calculate();
}. [item])
useMemo는 useEffect처럼 첫 번째 인자로 콜백함수, 두 번째 인자로 의존성 배열을 받습니다.
첫번째 인자인 콜백 함수는 메모이제이션을 해줄 값을 계산해서 리턴해주는 함수입니다.
즉 이 콜백함수가 리턴하는 값이 바로 useMemo가 리턴하는 값이 됩니다.
두 번째 인자는 의존성 배열입니다. 의존성 배열 안에 있는 값이 업데이트 될 때에만 콜백 함수를 다시 호출하여 메모리에 저장된 값을 업데이트 해줍니다.
만약 빈 배열을 넣는다면 useEffect와 마찬가지로 마운트 될 때에만 값을 계산하고 그 이후론 계속 memoization된 값을 꺼내와 사용합니다.
복잡한 계산에서의 useMemo 예제
import { useState, useMemo } from 'react';
const expensiveCalculation = (a, b) => {
// 복잡하고 길고 비용이 많이 드는 코드…
return a * b;
};
const Calculator = ({ a, b }) => {
const result = useMemo(() => expensiveCalculation(a, b), [a, b]);
return <div>결과값: {result}</div>;
};
const App = () => {
const [a, setA] = useState(5);
const [b, setB] = useState(10);
return (
<div>
<input
type="number"
value={a}
onChange={e => setA(parseInt(e.target.value))}
/>
<input
type="number"
value={b}
onChange={e => setB(parseInt(e.target.value))}
/>
<Calculator a={a} b={b} />
</div>
);
};
export default App;
값을 입력하는 input값의 변화에 따라 a와 b 값이 변경되고 expensiveCalculation이 실행됩니다.
useMemo를 사용하여 a와 b를 의존성 배열에 포함하고 a와 b 가 실제로 변경되는 경우에만 expensiveCalculation을 호출합니다.
>>불필요한 연산을 방지
리액트 공식문서에 따르면 useMemo는 비용이 많이 드는 계산을 캐싱하기 위해 사용되는 훅이며, 수천개의 객체를 만들거나 반복해야 할 일을 "비용이 많이 드는 계산" 이라고 합니다.
useCallback
useCallback이란?
메모이제이션 된 콜백 함수, 즉 이미 생성된 함수를 반환하는 리액트 훅입니다.
useCallback을 사용해야 하는 경우
- 이벤트 핸들러 함수가 자주 재생성 되는 경우
- useCallback을 사용하지 않으면 이벤트 함수는 매번 새로운 인스턴스가 생성됩니다. 그러나 useCallback을 사용하면 함수가 처음 생성될 때 한번만 생성되며, 나중에는 동일한 함수 인스턴스를 재사용하게 됩니다.
- 하위 컴포넌트에 props로 전달되는 함수가 자주 재생성되는 경우
- 리액트에서 props로 함수를 전달하는 경우, 해당 함수가 변경되면 하위 컴포넌트가 재렌더링됩니다. 따라서 useCallback을 사용하여 함수를 재사용하면 하위 컴포넌트의 재렌더링을 방지할 수 있습니다.
- 렌더링 최적화가 필요한 경우
- useCallback을 사용하여 함수를 재사용하면, 렌더링 최적화를 수행할 수 있습니다. 이는 컴포넌트의 불필요한 재렌더링을 방지하고 성능을 향상시킬 수 있습니다.
useCallback 사용방법
const memoizedCallback = useCallback(() => {
doSomething(a, b)
}, [a, b])
useCallback 과 마찬가지로 첫 번째 인자로 콜백함수, 두 번째 인자로 의존성 배열을 받습니다.
첫번째 인자로 넘긴 콜백함수를, 두번째 인자로 넘긴 의존성 배열 내의 값이 변경되기 전까지 저장하고 재사용할 수 있게 해줍니다.
만약 useCallback을 사용하지 않는다면, 아래와 같은 함수는 컴포넌트가 렌더링 될 때마다 새롭게 생성됩니다
const sum = () => x + y
하지만, useCallback을 사용하면 컴포넌트가 다시 렌더링 되더라도, 해당 함수가 의존하고 있는 값들이 바뀌지 않는다면 함수를 새로 생성하지 않고 기존 함수를 계속 반환합니다.
const add = useCallback(() => x + y, [x, y])
useCallback의 간단한예시
import { useState, useCallback } from "react";
import "./styles.css";
import List from "./List";
export default function App() {
const [input, setInput] = useState(1);
const [light, setLight] = useState(true);
const theme = {
backgroundColor: light ? "White" : "grey",
color: light ? "grey" : "white"
};
const getItems = useCallback(() => {
return [input + 10, input + 100];
}, [input]);
const handleChange = (event) => {
if (Number(event.target.value)) {
setInput(Number(event.target.value));
}
};
return (
<>
<div style={theme} className="wall-paper">
<input
type="number"
className="input"
value={input}
onChange={handleChange}
/>
<button
className={(light ? "light" : "dark") + " button"}
onClick={() => setLight((prevLight) => !prevLight)}
>
{light ? "dark mode" : "light mode"}
</button>
<List getItems={getItems} />
</div>
</>
);
}
useCallback을 쓰지 않을 때는 dark mode 버튼을 누를 때도 리렌더링 되면서 함수가 다시 만들어집니다.
List 컴포넌트 내 주소 값이 달라진 getItems이 전달됩니다.
그러나 useCallback을 쓰면 함수를 반복해서 생성하지 않습니다.
마무리 정리
useCallback: 이벤트 핸들러와 같은 함수를 메모이제이션 할 때 사용되는 훅입니다. 컴포넌트가 리렌더링 될 때마다 함수를 새로 생성하는 것을 방지하고, 이전에 생성한 함수를 재사용하여 불필요한 렌더링을 줄여줍니다.
useMemo: 렌더링 중에 발생하는 연산량이 큰 함수의 결과값을 메모제이션 하며, 이전 결과값을 재사용할 수 있도록 도와줍니다.
useCallback과 useMemo는 성능 최적화의 목적으로 사용되긴 하지만 무분별하게 사용할 경우 오히려 성능 저하를 초래할 수 있습니다.
- 메모이제이션 자체의 비용: 이 두 훅을 사용하면 함수와 계산 결과를 캐싱하기 위한 메모리 사용량이 늘어납니다. 새롭게 계산되는 값이 일정기간 동안 사용되지 않아도 메모리에 남아 있어야 하므로 메모리 관리 측면에서 비효율적일 수 있습니다.
- 의존성 배열의 관리 : useCallback과 useMemo는 의존성 배열이 필요한데, 이 배열이 들어간 값들이 변경 될 때 마다 메모이제이션 된 값을 무효화하고 새로 계산합니다. 이 과정에서 복잡성이 증가하며, 관리가 미흡한 경우 오히려 성능이 저하될 수 있습니다.
결론
모든 성능 최적화에는 비용이 따릅니다.
useCallback과 useMemo는 신중하게 사용되어야 하고, 필요한 경우에만 적용하여 성능 최적화를 추구하는 것이 좋습니다.
진짜 성능 이슈가 있는 곳에서만 해당 Hook들을 사용하고, 대부분의 상황에서는 useCallback,useMemo를 사용하지 않는 편이 성능, 가독성 측면에 이점이 있을 것입니다.
'React 공식문서 스터디' 카테고리의 다른 글
useDefferedValue (0) | 2024.06.25 |
---|---|
useTransition (0) | 2024.06.24 |
useCallback (1) (0) | 2024.06.18 |
useMemo (2) (0) | 2024.06.18 |
useMemo (1) (0) | 2024.06.17 |