React 성능 최적화 훅 useCallback
이번 강의에서는 성능 최적화를 위한 다음 훅을 살펴볼 것입니다.
useCallback.
useCallback 훅은 API useMemo와 유사하지만,
차이점은 전자가 리렌더링 사이에 값을 캐시하는 반면,
후자는 콜백을 캐시한다는 것입니다.
이를 통해 필요하지 않을 때 리소스 집약적인
함수를 재실행하지 않도록 할 수 있으며,
함수를 전달할 때
자식 컴포넌트에서 사용할 수 있습니다.
예제를 통해 자세히 알아봅시다.
먼저 App 컴포넌트를 만들고
상태 num를 선언하겠습니다:
const [num, setNum] = useState(0);
클릭할 때마다 num이
1씩 증가하는 버튼과,
num 값을 출력할 단락을 만들어 보겠습니다:
return (
<div>
<button onClick={() => setNum(num + 1)}>클릭</button>
<p>클릭 수: {num}</p>
</div>
);
이제 App에 다른 버튼을 눌러
항목을 추가할 수 있는 목록이
더 출력된다고 가정해 보겠습니다.
이 목록의 항목을 저장하기 위해
상태 items를 선언하겠습니다:
const [items, setItems] = useState([]);
그런 다음 항목을 추가하는 함수
addItem를 작성하겠습니다:
function addItem() {
setItems([...items, '새 항목']);
}
이제 목록 항목을 표시하는 코드를 작성하고
이를 자식 컴포넌트 Items로 분리하겠습니다.
이 컴포넌트는 props로 항목 배열과
항목 추가 함수를 받습니다.
Items가 리렌더링되는 시점을 확인하기 위해
콘솔 출력을 추가하는 것을 잊지 마세요:
function Items({ items, addItem }) {
const result = items.map((item, index) => {
return <p key={index}>{item}</p>;
});
console.log('Items 리렌더링');
return (
<div>
<h3>우리의 항목들</h3>
{result}
<button onClick={addItem}>항목 추가</button>
</div>
);
}
export default Items;
App 컴포넌트 끝부분에 Items를 배치하고,
배열 items와 항목 추가 함수
addItem를 전달하겠습니다:
return (
<>
<div>
<button onClick={() => setNum(num + 1)}>클릭</button>
<p>클릭 수: {num}</p>
<br />
</div>
<Items items={items} addItem={addItem} />
</>
);
이제 버튼을 클릭해 보면
num이 증가하고 새 항목이 목록에
추가되는 것을 확인할 수 있습니다.
콘솔을 열어보면, num을 증가시키는
버튼을 클릭할 때마다 목록이 매번
리렌더링된다는 것을 볼 수 있습니다.
목록이 작으면 문제가 없지만, 목록이
방대하고 많은 내용이 포함될 것으로 예상된다면 어떨까요?
문제없다 - 여러분은 말할 것입니다. 왜냐하면 지난
강의에서 컴포넌트의 불필요한 리렌더링을
피하기 위해 API memo를 배웠기 때문입니다.
그럼 우리의 컴포넌트
Items를 memo로 감싸면 모든 게 해결됩니다.
참고로 이것은 Items를 내보낼 때
바로 수행할 수 있습니다:
export default memo(Items);
memo를 가져오는 것을 잊지 마세요:
import { memo } from 'react';
이제 콘솔을 열고 버튼을 클릭해 보세요.
모든 노력이 허사입니다! 컴포넌트를
memo화했지만, '클릭' 버튼을 누르면
컴포넌트 Items가 여전히
매번 리렌더링됩니다.
그 이유는 부모 컴포넌트가 리렌더링될 때,
그 함수들이 새로 생성되기 때문입니다 - 이는
Items에 전달하는
함수 addItem에도 적용됩니다.
바로 이때 useCallback 훅이 도움이 됩니다.
적용해 봅시다. 먼저 App에서 가져오겠습니다:
import { useCallback } from 'react';
그런 다음 간단한 함수 선언
addItem를
함수 표현식으로 변경하고,
첫 번째 매개변수로 콜백 형태의 함수를
useCallback에 지정합니다.
두 번째 매개변수로 대괄호 안에 의존성을 지정합니다 - 함수에 참여하는 모든 반응형 변수,
이 경우에는 배열 items입니다:
const addItem = useCallback(() => {
setItems(() => [...items, '새 항목']);
}, [items]);
완료! 이렇게 하여 함수를 캐시했습니다.
다시 버튼을 클릭해 보면, 이제 '클릭' 버튼을
누를 때 자식 컴포넌트가
리렌더링되지 않는 것을 볼 수 있습니다.
App 컴포넌트를 만들고
텍스트가 있는 단락을 넣으세요.
초기값이 '텍스트'인
상태를 선언하고 단락에 출력하세요.
단락을 클릭하면 텍스트 끝에
느낌표가 추가되도록 하세요.
새 제품을 추가하는 버튼이 있는
자식 컴포넌트 Products를 만드세요.
이를 App에 배치하세요.
부모 컴포넌트에서 제품 배열을 위한 상태와
새 제품 추가 함수를 만드세요.
이를 props로 자식 컴포넌트에 전달하고,
ul 목록 형태로 전달된 배열을 출력하세요.
Products에서 콘솔에 텍스트
'products 리렌더링'를 출력하세요.
Products를 memo로 감싸세요.
단락과 버튼을 클릭해 보세요.
단락을 클릭할 때 자식 컴포넌트가
여전히 리렌더링되는지 확인하세요.
useCallback 훅으로 감싸
제품 추가 함수를 캐시하세요.
단락과 버튼을 클릭해 보세요.
단락을 클릭할 때 자식 컴포넌트가
더 이상 리렌더링되지 않는지 확인하세요.