1. useMemo
useMemo는 메모이제이션하여 불필요한 계산을 방지하는 React Hook이다. 즉, 특정 값이 변경되지 않으면 기존 계산 결과를 재사용하도록 하는 기능이라고 볼 수 있다.
주요 목적은 무거운 계산이나, 렌더링 중 반복적으로 실행되는 연사을 최소화하여 성능을 향상시키는 것이다.
import React, { useMemo } from 'react';
const memoizedValue = React.useMemo(() => computeExpensiveValue(a, b), [a, b]);
import로 useMemo를 가져온다.
useMemo(() => {계산식}, [의존성])
- 첫 번째 인자: 함수 -> 계산할 코드를 함수로 작성
- 두 번째 인자: 의존성 배열 -> 배열 안의 값이 변경될 때만 첫 번째 함수가 다시 실행됨
- 반환값: 메모이제이션된 결과 값
1-1. 계산 최적화
예시코드
import React, { useMemo } from "react";
function ExpensiveCalculation({ num }) {
const computeFactorial = (n) => {
console.log("Factorial 계산 중...");
return n <= 1 ? 1 : n * computeFactorial(n - 1);
};
// useMemo로 계산 최적화
const factorial = useMemo(() => computeFactorial(num), [num]);
return (
<div>
<p>Factorial of {num}: {factorial}</p>
</div>
);
}
- num 값이 바뀔 때만 computeFactorial 함수가 실행됨
- 그렇지 않으면 이전 계산 결과를 재사용하여 성능을 높임
import React, { useMemo } from "react";
function List({ items, filterText }) {
const filteredItems = useMemo(() => {
console.log("리스트 필터링 중...");
return items.filter((item) => item.includes(filterText));
}, [items, filterText]);
return (
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
- items와 filterText가 바뀌지 않으면 filteredItems를 다시 계산하지 않음
1-2. useMemo를 왜 써야 하는가?
- 복잡한 계산 로직을 매번 실행하지 않아 CPU를 낭비를 줄여 성능 개선을 한다.
- 부모 컴포넌트가 리렌더링되어도 의존성이 변경되지 않았다면 하위 컴포너느에 재계산을 전달하지 않음, 렌더링 최소화
- 비즈니스 로직과 UI 코드를 분리히여 가독성 증가시킨다.
1-3. 사용 시 주의해야 할 점
1. 사용하지 말아야 할 때
- 계산 비용이 크지 않거나 간단한 경우 굳이 useMemo를 사용하지 않아도 됨
- 과도한 사용은 오히려 가독성을 떨어뜨림
2. 의존성 배열의 관리
- 의존성 배열의 값을 정확히 관리하지 않으면 메모이제이션이 제대로 동작하지 않거나, 불필요한 재계산이 발생한다.
정리하자면,
useMemo는 같은 입력에는 같은 출력을 보장하고, 같이 변경될 때만 연산을 실행하게 해주는 강력한 도구이다. 잘 사용하면 React 앱의 성능을 눈에 띄게 개선할 수 있다.
2. React.memo
React.memo는 컴포넌트 메모이제이션을 통해 불필요한 재렌더링을 방지하는 고차 컴포넌트(HOC)이다.
- 기본적으로 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 함께 리렌더링되는데, React.memo는 props가 변경되지 않으면 리렌더링을 건너뛰게 함
2-1. 예제
import React, { memo } from 'react';
const ChildComponent = memo(({ count }) => {
console.log("ChildComponent 렌더링!");
return <div>Count: {count}</div>;
});
function ParentComponent() {
const [count, setCount] = React.useState(0);
const [text, setText] = React.useState("");
return (
<div>
<ChildComponent count={count} />
<button onClick={() => setCount(count + 1)}>증가</button>
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
);
}
- ChildComponent는 count prop이 변경될 때만 렌더링됨
- text 값이 변경되어도 ChildComponent는 리렌더링되지 않음
2-2. React.memo의 한계
- 같은 비교를 하지 않음 : props의 값이 객체나 배열일 경우 참조(메모리 주소)가 바뀌면 여전히 리렌더링이 발생
- useMemo나 useCallback으로 props를 메모이제이션하여 해결한다.
2-3. React.memo로 커스텀 비교 함수 사용
import React, { memo } from 'react';
const ChildComponent = memo(
({ data }) => {
console.log("렌더링!");
return <div>{data.value}</div>;
},
(prevProps, nextProps) => {
return prevProps.data.value === nextProps.data.value;
}
);
- 두 번째 인자로 커스텀 비교 함수(areEqual)를 추가해 세부적인 비교가 가능하다.
3. useCallback
useCallback은 함수를 메모이제이션하는 Hook으로, 함수의 참조(메모리 주소)가 바뀌는 것을 방지하여 자식 컴포넌트의 불필요한 렌더링을 줄이는 역할을 함
3-1. 사용 예제
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onClick }) => {
console.log("ChildComponent 렌더링!");
return <button onClick={onClick}>클릭!</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
// useCallback으로 함수 메모이제이션
const handleClick = useCallback(() => {
console.log("Button clicked!");
}, []);
return (
<div>
<ChildComponent onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>카운트 증가</button>
</div>
);
}
- handleClick은 useCallback 덕분에 참조값이 바뀌지 않아, ChildComponent는 리렌더링되지 않음
- 의존성 배열([])을 통해 언제 메모이제이션을 해제할 지 결정할 수 있다.
3-2. React.memo와 useCallback의 차이점
특징 | React.memo | useCallback |
역할 | 컴포넌트 자체를 메모이제이션 | 함수를 메모이제이션 |
사용 대상 | React 컴포넌트 | 함수 |
주요 목적 | 불필요한 컴포넌트 렌더링 방지 | 함수 참조값 변경 방지 |
의존성 관리 | 직접 props 변경 여부를 확인 | 의존성 배열을 통해 값이 바뀔 때만 함수 갱신 |
3-3. React.memo와 useCallback의 조합
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onClick, value }) => {
console.log("ChildComponent 렌더링!");
return (
<div>
<button onClick={onClick}>Value: {value}</button>
</div>
);
});
function ParentComponent() {
const [value, setValue] = useState(0);
const [text, setText] = useState("");
const handleClick = useCallback(() => {
setValue((prev) => prev + 1);
}, []);
return (
<div>
<ChildComponent onClick={handleClick} value={value} />
<input value={text} onChange={(e) => setText(e.target.value)} />
</div>
);
}
- React.memo: ChildComponent는 onClick과 value가 변경될 때만 리렌더링
- useCallback: handleClick 함수의 참조값이 변경되지 않아 불필요한 리렌더링 방지
4. 정리
- React.memo는 컴포넌트를 메모이제이션하여 렌더링을 최적화
- useCallback은 함수를 메모이제이션하여 컴포넌트가 받는 함수 props의 참조값을 유지
- 이는 둘 보완적인 관계로 함께 사용할 시에 성능 최적화에 도움이 된다.
'React > 다시 공부하는 리액트' 카테고리의 다른 글
React useOutletContext (0) | 2024.11.26 |
---|---|
React Router (0) | 2024.11.22 |
React useState, useReducer (1) | 2024.11.20 |
React strictMode / 순수 컴포넌트 (0) | 2024.11.20 |
React clearInterval() (0) | 2024.11.20 |