useMemo란?
컴퓨터나 스마트폰에서 프로그램이 작업을 더 빠르고 효율적으로 할 수 있게 돕는 도구이다.
반복해서 계산해야 하는 값이 있다면 미리 저장해두었다가 다음에 바로 꺼내어 쓸 수 있다.
useMemo의 사용법
const cachedValue = useMemo(calculateValue, dependencies)
- calculateValue : 캐시 저장할 함수
- dependencies : calculateValue 코드 내에 있는 모든 반응형 값(props, state, 모든 변수와 함수 포함 가능)
- 메모이제이션( memoization ): 'useMemo'는 함수의 반환값을 기억하고, 의존성 배월의 값이 변경될 때만 함수가 다시 실행된다. 그렇지 않으면 이전에 계산된 값을 반환한다.
* 메모이제이션(memoization)란? 한 번 계산한 값을 기억해두고 다시 계산하지 않는 것을 뜻 한다.
- 최적화 : 리렌더링할 때마다 비싼 계산을 반복하지 않고, 성능을 최적화할 수 있다.
useMemo 장점
- 시간 절약 : 한 번 계산한 것을 기억해두고 다시 쓸 수 있어서 시간이 절약된다.
- 빠른 처리 : 프로그램이 더 빠르게 작동하게 도와준다.
- 효율적 사용 : 컴퓨터의 자원을 더 효율적으로 사용할 수 있게 해준다.
useMemo 언제 사용할까?
- 복잡한 계산: 데이터 필터링, 정렬, 복잡한 수학 연산 등 시간이 오래 걸리는 작업을 할 때
- 자주 리렌더링되는 컴포넌트 : 부모 컴포넌트가 자주 리렌더링 되어 자식 컴포넌트까지 불필요하게 리렌더링 될 때
- 데이터 변환 : 데이터를 복잡하게 변환해야 할 때, 변환 결과를 메모이제이션해서 효율적으로 사용할 때
사용 예제 보기
import { useState, useMemo } from 'react';
function ExpensiveComponent() {
const [number, setNumber] = useState(1);
const [anotherNumber, setAnotherNumber] = useState(0);
// 계산 비용이 큰 작업을 흉내내는 함수
const expensiveCalculation = (num) => {
console.log('Calculating...');
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};
// number 값이 바뀔 때만 expensiveCalculation이 재실행됨
const memoizedValue = useMemo(() => expensiveCalculation(number), [number]);
return (
<div>
<h1>Expensive Calculation</h1>
<p>Number: {number}</p>
<p>Another Number: {anotherNumber}</p>
<p>Result of Expensive Calculation: {memoizedValue}</p>
<button onClick={() => setNumber(number + 1)}>Increment Number</button>
<button onClick={() => setAnotherNumber(anotherNumber + 1)}>Increment Another Number</button>
</div>
);
}
export default ExpensiveComponent;
위 코드를 보면 setAnotherNumber의 상태값이 변해도 useMemo를 사용한 setNumber는 리렌더링 되지 않고 저장된 값을 반환하는 것을 알 수 있다.
실제 사용 예제
import React, { useState, useMemo } from 'react';
const products = [
{ id: 1, name: 'Apple', category: 'Fruit' },
{ id: 2, name: 'Broccoli', category: 'Vegetable' },
{ id: 3, name: 'Carrot', category: 'Vegetable' },
{ id: 4, name: 'Banana', category: 'Fruit' },
];
function ProductList() {
const [selectedCategory, setSelectedCategory] = useState('All');
// 카테고리에 따른 필터링 함수
const filterProductsByCategory = (products, category) => {
console.log('Filtering products...');
if (category === 'All') return products;
return products.filter(product => product.category === category);
};
// selectedCategory가 변경될 때만 필터링 작업이 실행됨
const filteredProducts = useMemo(() => {
return filterProductsByCategory(products, selectedCategory);
}, [selectedCategory]);
return (
<div>
<button onClick={() => setSelectedCategory('All')}>Show All</button>
<button onClick={() => setSelectedCategory('Fruit')}>Show Fruits</button>
<button onClick={() => setSelectedCategory('Vegetable')}>Show Vegetables</button>
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
export default ProductList;
useCallback이란?
리액트에서 함수를 메모이제이션(memoization)하여 불필요한 함수 재생성을 방지하고 성능을 최적화하기 위해 사용하는 훅이다.
useCallback이 왜 필요한가?
리액트에서는 컴포넌트가 리렌더링될 때마다 그 안에서 정의된 함수도 다시 생성된다. 하지만 이렇게 함수를 계속 재생성하면 자식 컴포넌트에 불필요한 리렌더링을 유발할 수 있다. 이를 해결할 수 있는 도구이다.
useCallback의 사용법
const memoizedCallback = useCallback(() => {
// 함수 내용
}, [dependencyArray]);
- 첫번째 인자 {} : 메모이제이션할 함수
- 두번째 인자 [] : 의존성 배열. 배열 안에 있는 값들이 바뀔 때만 함수를 다시 생성한다. [a, b]라면 a나 b가 바뀔 때만 함수를 다시 만들도록 설정한다.
사용 예제 보기
import React, { useState, useCallback } from 'react';
// Button 컴포넌트
const Button = React.memo(({ onClick, label }) => {
console.log(`Button with label "${label}" rendered`);
return <button onClick={onClick}>{label}</button>;
});
function Counter() {
// 상태 정의
const [count, setCount] = useState(0);
const [otherCount, setOtherCount] = useState(0);
// useCallback으로 숫자를 증가시키는 함수 메모이제이션
const incrementCount = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
// useCallback 없이 다른 카운터를 증가시키는 함수
const incrementOtherCount = () => {
setOtherCount(prevCount => prevCount + 1);
};
return (
<div>
<p>Count: {count}</p>
<Button onClick={incrementCount} label="Increment Count" />
<p>Other Count: {otherCount}</p>
<Button onClick={incrementOtherCount} label="Increment Other Count" />
</div>
);
}
export default Counter;
화면에 띄워 콘솔창을 보면 useCallback을 사용한 함수 incrementCount의 반환 값은 처음 화면에 렌더링될 때만 콘솔이 찍혀 있고 이후에 두개의 버튼을 아무리 눌러도 더 이상 콘솔에 찍히지 않는 반면,
사용하지 않은 incrementOtherCount 함수는 두 개의 버튼 모두 누를 때 마다 리렌더링 되어 콘솔에 계속 찍힌다.
* count가 아닌 prevCount라고 쓴 이유
: prevCount는 별도의 상태가 아닌 이전 상태 값을 의미하는 함수의 인자이다. 여러 번의 상태 업데이트가 거의 동시에 발생할 때, 이전 상태를 기준으로 새로운 상태를 계산하면 예상치 못한 결과를 방지할 수 있다.
* React.memo는 React에서 사용되는 함수 중 하나로, 성능 최적화를 위해 컴포넌트를 감싸는 역할을 한다.
참고 자료
https://lyuna29.tistory.com/45
useCallback: 이벤트 핸들러와 같은 함수를 메모이제이션 할 때 사용되는 훅입니다. 컴포넌트가 리렌더링 될 때마다 함수를 새로 생성하는 것을 방지하고, 이전에 생성한 함수를 재사용하여 불필요한 렌더링을 줄여줍니다.
useMemo: 렌더링 중에 발생하는 연산량이 큰 함수의 결과값을 메모제이션 하며, 이전 결과값을 재사용할 수 있도록 도와줍니다.
useCallback과 useMemo는 성능 최적화의 목적으로 사용되긴 하지만 무분별하게 사용할 경우 오히려 성능 저하를 초래할 수 있습니다.
- 메모이제이션 자체의 비용: 이 두 훅을 사용하면 함수와 계산 결과를 캐싱하기 위한 메모리 사용량이 늘어납니다. 새롭게 계산되는 값이 일정기간 동안 사용되지 않아도 메모리에 남아 있어야 하므로 메모리 관리 측면에서 비효율적일 수 있습니다.
- 의존성 배열의 관리 : useCallback과 useMemo는 의존성 배열이 필요한데, 이 배열이 들어간 값들이 변경 될 때 마다 메모이제이션 된 값을 무효화하고 새로 계산합니다. 이 과정에서 복잡성이 증가하며, 관리가 미흡한 경우 오히려 성능이 저하될 수 있습니다.
결론
모든 성능 최적화에는 비용이 따릅니다.
useCallback과 useMemo는 신중하게 사용되어야 하고, 필요한 경우에만 적용하여 성능 최적화를 추구하는 것이 좋습니다.
진짜 성능 이슈가 있는 곳에서만 해당 Hook들을 사용하고, 대부분의 상황에서는 useCallback,useMemo를 사용하지 않는 편이 성능, 가독성 측면에 이점이 있을 것입니다.
'React-study > presentation' 카테고리의 다른 글
[9장 발표] 모던 리액트 개발 도구로 개발 및 배포 환경 구축하기 (1) | 2024.12.08 |
---|---|
[모던리액트 Deep Dive] 5장 발표자료 (2) | 2024.11.15 |
[발표] useTransition과 useDeferredValue (0) | 2024.06.25 |
[발표] useEffect와 useLayoutEffect (0) | 2024.06.04 |
[발표] useReducer / useContext / useRef (0) | 2024.05.30 |