목차
· 메모이제이션이란?
· 리액트에서 제공하는 메모이제이션 방법
· useCallback과 useMemo와 같은 메모이제이션 훅을 많은 곳에 사용하면 되는거 아니예요?
리액트는 컴포넌트 기반 아키텍처를 제공하여 개발자가 재사용 가능한 코드를 작성하고, 이를 통해 보다 편안한 사용자 경험을 제공할 수 있어요. 다른 기술과 마찬가지로 이를 잘 활용한다면 코드를 효율적이고, 사용하기 쉽게 만드는데 도움이 될 수 있어요. 리액트에서는 재사용이라는 강력한 특징을 제공하고 있는데, 이 재사용이 사용되지 않아도 되는 곳에도 재사용이 과도하게 사용되는 거라면 이는 리소스의 낭비로도 이어질 수 있어요. 오히려 비효율적인 방법이 될 수도 있는거죠. 효율적인 재사용을 하기 위해 리액트에서는 내부적으로 성능을 최적화할 수 있도록 훅을 제공하고 있어요. 오늘 이 시간에는 리액트에서 제공하는 훅을 통해 성능을 최적화하는 방법에 대해 알아보려고 해요.
메모이제이션이란?
메모이제이션이란 복잡한 함수 호출의 결과를 캐싱하고 동일한 입력이 다시 발생할 때 캐싱된 결과를 반환하는 기술이에요. 동일한 입력으로 여러 번 호출되는 함수 또는 컴포넌트가 있을 때 주로 사용이 되며, 메모이제이션을 사용하면 동일한 결과를 불필요하게 다시 계산하지 않고, 캐시된 결과를 반환할 수 있어요. 따라서 메모이제이션을 통해 성능을 향상시키고, 코드의 복잡성을 줄일 수 있는 것이죠.
리액트에서 제공하는 메모이제이션 방법
대표적으로 리액트에서 제공하는 성능 최적화 방법에 대한 훅은 React.memo · useMemo · useCallback 이 있어요. 한번 각각 어떤 역할을 하고, 어떻게 사용하는지 같이 한번 살펴보아요.
React.memo
React.memo를 통해 컴포넌트를 메모이제이션 할 수 있어요. 컴포넌트의 props가 변경되지 않으면 컴포넌트를 리렌더링하지 않도록 하고, 특히 부모 컴포넌트가 자주 업데이트되는 상황에서 유용하게 사용이 될 수 있어요.
const MemoizedComponent = React.memo(YourComponent)
useMemo
useMemo 훅을 통해 값에 대한 메모이제이션을 할 수 있어요, useMemo는 계산 비용이 많이 드는 연산 결과를 메모이제이션해요.
useMemo는 의존성 배열에 있는 값이 변경되었을 때에만 메모이제이션된 값을 다시 계산해요, 따라서 매렌더링마다 실행되었던 복잡한 계산을 방지해주어요. (만약 의존성 배열이 없는 경우에는 매렌더링마다 새로운 값을 계산) 컴포넌트 안에서 여러 값들을 관리할 때, 하나의 값을 변경했음에도 불구하고 다른 값까지 리렌더링되는 경우에는 useMemo의 메모이제이션을 통해 성능 최적화를 진행할 수 있어요.
const memoizedValue = useMemo(() => computedValue(a, b), [a, b]);
useCallback
useCallback 훅을 통해 함수에 대한 메모이제이션을 할 수 있어요, 자식 컴포넌트에 전달되는 콜백 함수를 메모이제이션 해요. useCallback도 useMemo와 동일하게 의존성 배열의 값이 변경되었을 때만 다시 계산을 진행해요. 불필요한 렌더링을 방지하기 위해 참조의 동일성을 보장하거나, 자식 컴포넌트에 의존적인 콜백 함수를 전달할 때 유용하게 사용될 수 있어요. useCallback의 경우 함수를 특정 조건이 변경되지 않는 이상 재생성하지 못하게 제한하여 함수의 동등성을 보장할 수 있어요, 함수도 참조를 유지하지 않으면 컴포넌트가 리렌더링 되면서 새로운 참조를 통해 기존에 함수 참조를 찾지 못하는 경우도 있거든요.
const memoizedCallback = useCallback(() => yourCallbackFunc(a, b), [a, b]);
/* 위의 예제는 useMemo의 해당 코드와 비슷해요
* useMemo(() => yourCallbackFunc(a, b), [a, b]); */
그러면 성능 최적화에 대한 태스크가 생겼을 때,
useCallback과 useMemo와 같은 메모이제이션 훅을 많은 곳에 사용하면 되는거 아니예요?
메모이제이션을 무분별하게 적용하는 것은 오히려 성능에 부정적인 영향을 미칠 수 있어요. 메모이제이션도 결국 또 다른 내부적인 계산에 대한 비용이 발생하는 작업이에요, React는 이전 값을 메모리에 저장해야하므로 메모리 비용을 발생하고, 의존성 배열의 각 항목을 비교하는 작업을 수행해야해요.
// 단순 로그를 출력하는 함수의 경우 메모이제이션이 불필요합니다.
const handleClick = useCallback(() => {
console.log('Its works!');
}, []);
// 복잡한 연산을 통해 값을 도출하는 경우에는 메모이제이션이 효율적입니다.
const computedValue = useMemo(() => {
return complexCalculation(items);
}, [items]);
복잡한 계산이 필요한 경우, 컴포넌트가 자주 리렌더링 되는 경우 등 성능 최적화가 필요한 경우에만 사용을 하는 것이 메모이제이션 측면에서 성능에 도움이 되어요. 따라서 성능 최적화는 실제 병목이 발생하는 지점을 디버깅을 통해 확인하고, 선별적으로 적용하는 것이 바람직해요. 디버깅은 React DevTools Profiler과 같은 툴을 통해 확인할 수 있어요.
댓글