리액트 컴포넌트를 개발하다 보면 예상치 못한 리렌더링으로 성능 저하를 겪는 경우가 있습니다.
이 문제를 해결하기 위해 useCallback이나 useMemo 같은 훅을 사용하곤 하죠.
이 훅들의 핵심 원리는 바로 '안정성(Stability)'입니다.
'안정성'이란 무엇일까요?
간단히 말해, 컴포넌트가 다시 렌더링되더라도 특정 값의 메모리 주소(Reference)가 변하지 않고 동일하게 유지되는 상태를 의미합니다. 이 개념을 이해하면 리액트 성능 최적화의 원리를 더 깊이 있게 파악할 수 있습니다.
메모리 주소(Memory Address)란?
메모리 주소를 쉽게 비유해 볼까요?
컴퓨터의 메모리(RAM)를 거대한 아파트 단지로 생각하면, 메모리 주소는 그 아파트의 동, 호수와 같습니다.
- 아파트 단지 전체: 컴퓨터의 메모리(RAM)
- 각 호수: 메모리 주소
- 호수에 사는 사람: 데이터(변수, 함수, 객체 등)
const name = 'React'라는 코드를 작성하면, 컴퓨터는 React라는 문자열을 메모리의 특정 호수(메모리 주소)에 저장하고,
name이라는 변수가 그 위치를 가리키게 됩니다.
리액트에서 메모리 주소가 중요한 이유
리액트 컴포넌트가 리렌더링될 때, 특별한 조치 없이 컴포넌트 내부에 정의된 객체나 함수는 매번 새로운 호수(메모리 주소)에 다시 만들어집니다.
const MyComponent = () => {
const myObject = {}; // 리렌더링마다 새로운 {}가 생성, 새로운 메모리 주소 할당
const myFunction = () => {}; // 리렌더링마다 새로운 함수가 생성, 새로운 메모리 주소 할당
// ...
};
이렇게 매번 다른 메모리 주소를 가지게 되는 값들은 useEffect와 같은 훅의 의존성 배열에 들어갈 때 문제를 일으킵니다. useEffect는 의존성 배열에 있는 값이 변경되었다고 판단할 때마다 재실행됩니다.
객체나 함수가 매 렌더링마다 새로 생성되면, useEffect는 불필요하게 반복 실행되어 성능 저하를 초래합니다.
이러한 문제를 해결하기 위해 우리는 useCallback이나 useMemo를 사용하여 값을 안정적으로 만듭니다. 이 훅들은 의존성 배열에 지정된 값이 변경되지 않는 한, 이전에 생성된 동일한 메모리 주소를 계속해서 사용하도록 합니다.
안정적인 값의 종류
모든 값이 불안정한 것은 아닙니다. 리액트 컴포넌트 내에서 기본적으로 안정성을 유지하는 값들과 불안정한 값을 안정적으로 만드는 도구들이 있습니다.
1. 기본적으로 안정적인 값들
- 원시 값(Primitive values)
: const로 선언된 숫자, 문자열, 불리언은 값이 변하지 않는 한 메모리 주소도 변하지 않아 안정적입니다. - useRef가 반환하는 객체
: useRef로 생성된 ref.current 값은 컴포넌트의 라이프사이클 전체에서 동일한 메모리 주소를 유지합니다. - 특정 라이브러리 함수
: React Hook Form의 watch, reset 같은 함수들은 내부적으로 메모이제이션이 적용되어 있어,
리렌더링되어도 함수 레퍼런스가 변하지 않는 안정적인 상태를 유지하도록 설계되어 있습니다.
2. 불안정한 값을 안정화시키는 도구
- useCallback: 함수를 메모이제이션(memoization)하여 안정적으로 만듭니다.
- useMemo: 값(객체, 배열 등)을 메모이제이션하여 안정적으로 만듭니다.
이 훅들은 의존성 배열에 지정된 값이 변경되지 않는 한, 이전에 생성된 동일한 메모리 주소를 계속해서 사용하도록 합니다.
// useCallback과 useMemo를 사용해 안정화된 예시
const MyComponent = () => {
const myStableObject = useMemo(() => ({}), []); // 리렌더링되어도 동일한 메모리 주소 유지
const handleStableClick = useCallback(() => {}, []); // 리렌더링되어도 동일한 메모리 주소 유지
// ...
};
마무리
결론적으로, 리액트에서 안정성은 불필요한 리렌더링을 막고,
useEffect와 같은 훅이 의도한 대로 동작하도록 보장하는 중요한 개념입니다.
매번 새로 생성되는 객체나 함수와 같은 '불안정한' 값들은 useCallback과 useMemo를 통해 '안정적'인 값으로 만들어,
성능 최적화의 기반을 다질 수 있습니다.
리액트 애플리케이션의 성능을 최적화하고 싶다면, useCallback과 useMemo를 사용하는 것뿐만 아니라,
'값의 안정성'이라는 근본적인 개념을 이해하는 것이 중요합니다.
이 원리를 제대로 이해하면 보다 효율적이고 견고한 코드를 작성할 수 있을 것입니다.
참고자료
'web' 카테고리의 다른 글
| [React] 상태관리와 훅 (1) | 2024.09.19 |
|---|---|
| [ 모던 리액트 : Deep Dive ] - 03장 리액트 훅 깊게 살펴보기 useReducer (3) | 2023.12.22 |
| [ 모던 리액트 : Deep Dive ] - 03장 리액트 훅 깊게 살펴보기 useContext (2) | 2023.12.22 |
| [ 모던 리액트 : Deep Dive ] - 03장 리액트 훅 깊게 살펴보기 useRef (0) | 2023.12.22 |
| [ 모던 리액트 : Deep Dive ] - 03장 리액트 훅 깊게 살펴보기 useMemo, useCallback (0) | 2023.12.22 |