- React.memo 없이도 최적화가 가능하다? 컴포지션과 상태 지역화2025년 08월 12일 16시 42분 04초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
React.memo 없이도 최적화가 가능하다? 컴포지션과 상태 지역화
이 글은 유승완님이 번역하신 글(https://imnotadevleoper.tistory.com/entry/번역-Reactmemo-완벽-해부-언제-쓸모-있고-언제-쓸모없는가)을 읽다가 궁금한 점이 있어서 찾아본 내용을 정리한 것이다.
1. React.memo를 활용한 불필요한 리렌더링 최적화
React 애플리케이션을 개발할 때 컴포넌트의 렌더링 최적화는 매우 중요한 과제이다. 특히 부모 컴포넌트의 상태 변경으로 인해 자식 컴포넌트가 불필요하게 리렌더링되는 경우가 빈번하게 발생한다. 이 글에서는 이러한 상황을 분석하고
React.memo를 사용하여 해결하는 방법을 알아본다.1-1. 문제 상황: 부모의 상태 변경과 자식의 리렌더링
아래 코드는 버튼 클릭 시 부모 컴포넌트의 상태(
count)가 변경되는 간단한 예시이다.const ParentWithState = () => { const [count, setCount] = useState(0); console.log("Parent re-rendered"); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> <ExpensiveComponent /> {/* count가 변경될 때마다 리렌더링 됨 */} </div> ); }; const ExpensiveComponent = () => { console.log("ExpensiveComponent re-rendered... Oh no!"); // 무거운 연산을 수행한다고 가정 return <div>I am an expensive component.</div>; };위 코드에서
Increment버튼을 클릭하면ParentWithState의count가 변경되어 리렌더링이 발생한다.이때
ExpensiveComponent는count와 아무런 관련이 없음에도 불구하고, 콘솔에 'ExpensiveComponent re-rendered... Oh no!'가 계속 출력되는 것을 볼 수 있다.이는 부모가 리렌더링될 때 자식 컴포넌트도 함께 리렌더링되는 React의 기본 동작 때문이다.
1-2. 해결 방안:
React.memo의 등장React.memo는 컴포넌트의 props가 변경되지 않았다면, 리렌더링을 방지하여 이전에 렌더링된 결과를 재사용하도록 하는 고차 컴포넌트(Higher-Order Component, HOC)이다.React.memo는 컴포넌트를 감싸주기만 하면 되며, 해당 컴포넌트는 오직 자신의 props가 변경될 때만 리렌더링된다.- 즉, 부모 컴포넌트가 리렌더링되더라도
React.memo로 감싸진 자식 컴포넌트의 props가 이전과 같다면 리렌더링을 건너뛴다.
1-3.
React.memo적용 방법ExpensiveComponent를React.memo로 감싸주기만 하면 간단하게 문제를 해결할 수 있다.
ExpensiveComponent수정- 기존의
ExpensiveComponent를React.memo()로 감싸서 새로운MemoizedExpensiveComponent를 생성한다.
import React, { memo } from "react"; const ExpensiveComponent = () => { console.log("ExpensiveComponent re-rendered... Oh no!"); return <div>I am an expensive component.</div>; }; // React.memo를 사용하여 컴포넌트를 메모이제이션한다. const MemoizedExpensiveComponent = memo(ExpensiveComponent);- 기존의
부모 컴포넌트에서 사용
- 부모 컴포넌트에서는
React.memo가 적용된MemoizedExpensiveComponent를 사용한다.
const ParentWithState = () => { const [count, setCount] = useState(0); console.log("Parent re-rendered"); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> {/* React.memo가 적용된 컴포넌트 사용 */} <MemoizedExpensiveComponent /> </div> ); };- 부모 컴포넌트에서는
- 이제 버튼을 클릭하여
count상태를 변경해도ParentWithState만 리렌더링될 뿐,MemoizedExpensiveComponent는 더 이상 불필요하게 리렌더링되지 않는다.
1-4. 결론
- React에서 부모 컴포넌트의 상태 변경은 기본적으로 모든 자식 컴포넌트의 리렌더링을 유발한다.
- 이로 인해 발생하는 불필요한 렌더링은
React.memo를 사용하여 최적화할 수 있다. React.memo는 컴포넌트의 props를 비교하여 변경 사항이 없을 경우 리렌더링을 방지함으로써 애플리케이션의 성능을 향상시키는 효과적인 도구이다. 따라서 props의 변화가 잦지 않은 무거운 컴포넌트에 적극적으로 활용하는 것이 좋다.
2. 상태의 위치가 리렌더링 범위에 미치는 영향
React에서 성능 최적화를 이해하는 데 있어 '상태(state)를 어디에 둘 것인가'는 매우 중요한 질문이며, 상태의 위치를 변경하는 것만으로도 불필요한 리렌더링을 막을 수 있다. 이번에는 상태가 부모가 아닌 형제 컴포넌트에 있을 때의 동작을 분석해 본다.
2-1. 코드 분석: 상태가 형제 컴포넌트에 있는 경우
아래 코드는 상태(
count)를Parent가 아닌, 실제 그 상태를 사용하는CounterButton컴포넌트가 직접 소유하고 있는 구조이다.const CounterButton = () => { const [count, setCount] = useState(0); console.log("CounterButton re-rendered"); return <button onClick={() => setCount(count + 1)}>Count: {count}</button>; }; const ExpensiveComponent = () => { console.log("ExpensiveComponent re-rendered... NOT THIS TIME!"); return <div>I am an expensive component.</div>; }; const Parent = () => { console.log("Parent re-rendered"); return ( <div> <CounterButton /> <ExpensiveComponent /> </div> ); };위 코드에서
CounterButton내부의 버튼을 클릭하면count상태가 변경된다.이때 콘솔을 확인해 보면 'CounterButton re-rendered'는 출력되지만, 'Parent re-rendered'나 'ExpensiveComponent re-rendered...'는 출력되지 않는다.
2-2.
ExpensiveComponent가 리렌더링되지 않는 이유- 리렌더링의 범위는 상태 변경이 일어난 컴포넌트와 그 자식들로 한정된다.
- 이 예제에서 상태 변경은
CounterButton컴포넌트 내부에서 시작되고 끝난다.setCount가 호출되면 React는CounterButton컴포넌트만 리렌더링 대상으로 지정한다. Parent컴포넌트는 자신의 상태나 props가 전혀 변경되지 않았으므로 리렌더링할 이유가 없다.- 부모인
Parent가 리렌더링되지 않았기 때문에, 그의 다른 자식인ExpensiveComponent역시 리렌더링되지 않는다.
2-3. 이전 예제와의 핵심적인 차이
이전 예제: 상태(
count)가Parent에 있었다. 따라서Parent가 리렌더링되었고, 이로 인해 그의 모든 자식들(CounterButton,ExpensiveComponent)이 영향을 받아 함께 리렌더링되었다.이번 예제: 상태(
count)가CounterButton에 있다. 따라서 오직CounterButton자신만 리렌더링되고, 부모나 형제 컴포넌트에는 아무런 영향을 주지 않는다.이처럼 상태를 실제로 사용하는 컴포넌트에 가깝게 위치시키는 것만으로도,
React.memo와 같은 별도의 최적화 기법 없이 불필요한 렌더링을 방지할 수 있다. 이를 **'상태의 지역화(State Colocation)'**라고도 한다.
2-4. 결론
- 컴포넌트의 리렌더링 여부는 상태(state)가 어디에 선언되어 있는지에 따라 결정된다.
- 상태가 특정 컴포넌트 내부에 완전히 캡슐화되어 있다면, 해당 상태의 변경은 오직 그 컴포넌트의 리렌더링만을 유발한다.
- 이는 React의 매우 중요한 원칙으로, 상태를 가능한 한 그 상태를 사용하는 컴포넌트와 가까운 곳에 두는 것이 불필요한 리렌더링을 막는 가장 자연스럽고 효과적인 방법이다.
3. 컴포지션과 상태의 지역화
React의 핵심 철학은 '컴포지션'에 기반하며, 이를 통해 '상태의 지역화'라는 성능 최적화 패턴을 자연스럽게 구현할 수 있다. 두 개념은 서로 뗄 수 없는 파트너와 같다. 이 둘을 명확히 구분하는 것은 React를 더 깊이 이해하는 데 매우 중요하다.
3-1. 컴포지션(Composition)이란? - '어떻게' 만들 것인가
- 컴포지션은 여러 개의 작은 독립적인 컴포넌트들을 조합하여 더 복잡한 UI를 만드는 React의 핵심 디자인 패턴이다. 이는 마치 레고 블록을 조립해 새로운 창작물을 만드는 것과 같다.
- 이전 예제에서
Parent컴포넌트가<CounterButton />와<ExpensiveComponent />를 자식으로 포함한 것 자체가 바로 컴포지션이다.const Parent = () => { return ( // Parent는 CounterButton과 ExpensiveComponent를 '소유'하여 '구성(합성)'된다. (Composition) <div> <CounterButton /> <ExpensiveComponent /> </div> ); }; - 즉, 컴포지션은 컴포넌트를 설계하고 조립하는 구조적인 방법론에 가깝다. "어떻게 UI를 구성할 것인가?"라는 질문에 대한 답이다.
3-2. 상태의 지역화(State Colocation)란? - '어디에' 둘 것인가
- 상태의 지역화는 상태(state)를 그 상태를 사용하는 가장 가까운 곳에 배치하는 성능 최적화 원칙이다.
CounterButton예제에서,count상태를Parent가 아닌CounterButton이 직접 소유하게 한 것이 바로 상태의 지역화이다.- 이 원칙을 따르면 상태 변경으로 인한 리렌더링의 영향 범위를 최소화할 수 있다. 상태가 변경되어도 오직 그 상태를 가진 컴포넌트와 그 자식들만 리렌더링되기 때문이다.
- 즉, 상태의 지역화는 "이 상태를 어디에 위치시켜야 가장 효율적일까?"라는 성능 관점의 질문에 대한 답이다.
3-3. 두 개념의 관계: 상호 보완적인 파트너
둘의 관계를 작업실에 비유하면 쉽게 이해할 수 있다.
- 컴포지션: 작업실의 공구들을 종류별로(드라이버, 망치, 렌치) 정리해 각각의 서랍에 보관하는 것과 같다. 이는 작업 공간을 구조화하는 행위이다.
- 상태의 지역화: 드라이버를 사용할 때, 필요한 나사들을 온 작업실에 흩어두지 않고, 드라이버 서랍 바로 옆 작은 통에 보관하는 것과 같다. 이는 작업을 효율화하는 행위이다.
정리하자면, 컴포지션은 상태의 지역화를 가능하게 하는 구조적 토대이다.
CounterButton이라는 독립된 컴포넌트(서랍)를 컴포지션을 통해 만들었기 때문에, 그 안에count라는 상태(나사)를 지역화할 수 있었던 것이다.3-4. 결론
- 사용자의 지적대로, 제가 보여준 예제는 컴포지션 패턴을 사용한 것이 맞다.
- 그리고 그 컴포지션 구조 안에서 상태의 지역화 원칙을 지켰기 때문에 불필요한 리렌더링이 발생하지 않는 성능상의 이점을 얻을 수 있었다.
- 따라서 두 용어는 다음과 같이 정리할 수 있다.
- 컴포지션 (Composition): 컴포넌트를 조립하는 구조적 원칙이다.
- 상태의 지역화 (State Colocation): 그 구조 내에서 상태를 배치하는 성능 최적화 원칙이다.
728x90반응형'언어·프레임워크 > React.js' 카테고리의 다른 글
React 로직 재사용 패러다임의 전환: HOC와 Hooks 분석 (0) 2026.01.06 [React.js] 리액트 19의 혁신적인 `use` 훅: 비동기 데이터와 컨텍스트를 더 스마트하게 (0) 2025.09.04 [React.js] Spring Boot와 연동 시 `Invalid URL` 오류 해결기(리액트 프로젝트 중단점 설정) (0) 2025.06.18 [React.js] 네이버 지도 API PDF 변환 시 CORS 오류 완벽 해결법 (0) 2025.06.16 [React.js] 메모이제이션 완벽 가이드: memo, useCallback, useMemo와 Profiler 활용 (0) 2025.05.25 다음글이 없습니다.이전글이 없습니다.댓글