미션 14에서 StockButton의 memo는 어떤 역할인가요
정답 코드에 ProductList 컴포넌트가 없어서 궁금한게 있는데요. StockButton컴포넌트는 어디에 위치시켜야하나요?
memo가 StockButton컴포넌트를 감싸는데 이 memo가 하는일은 무엇인가요?
Answer 2
0
안녕하세요 이태관님. 질문해주신 정답 코드에 생략된 컴포넌트의 위치와 보호 함수의 역할에 대해 코드를 바탕으로 로직의 흐름에 따라 자연스럽게 설명해 드리겠습니다.
정답 코드에서 생략된 ProductList 컴포넌트는 상태 채널인 ProductStateContext를 구독하여 상품 목록 데이터를 받아오게 됩니다. 그리고 각 상품 항목을 렌더링할 때 StockButton을 자식 컴포넌트로 포함하는 구조를 가집니다.
import React, { useContext } from 'react';
import { ProductStateContext } from '../contexts/ProductContext';
import { StockButton } from './StockButton';
export default function ProductList() {
const items = useContext(ProductStateContext);
return (
<ul>
{items.map((item) => (
<li key={item.id} style={{ marginBottom: '10px' }}>
<span>
{item.name} - {item.isSoldOut ? '품절' : '판매중'}
</span>
<StockButton id={item.id} />
</li>
))}
</ul>
);
}
이 코드에서 볼 수 있듯이 StockButton은 개별 상품을 식별하여 상태를 변경해야 합니다. 따라서 ProductList 내부에서 map 함수를 통해 반복 렌더링되는 각 상품 항목과 동일한 레벨에 위치하는 것이 로직상 가장 적절합니다.
이렇게 버튼이 배치된 상태에서 StockButton을 감싸고 있는 React.memo가 어떤 역할을 하는지 살펴보겠습니다. 이 함수의 주된 역할은 부모 컴포넌트가 리렌더링될 때, 자식 컴포넌트가 전달받는 속성인 props, 즉 여기서는 id 값이 변하지 않았다면 해당 자식 컴포넌트의 리렌더링을 막아주는 것입니다. 현재 구조에서는 특정 상품의 품절 여부와 같은 상태가 변경되면 items 배열 전체가 업데이트됩니다. 이렇게 되면 이를 직접 구독하고 있는 부모 컴포넌트인 ProductList에 리렌더링이 발생하게 됩니다. React의 기본 동작 원리상 부모 컴포넌트가 렌더링되면 그 내부에 있는 자식 컴포넌트들도 함께 다시 그려집니다.
하지만 여기서 매우 중요한 실무적 함정이자 아키텍처 개선 포인트를 짚고 넘어가야 합니다. 현재 구조처럼 ProductList 내부에서 items.map을 돌려 개별 <li> 요소와 StockButton을 직접 배치할 경우, ProductList가 다시 실행될 때 map 함수도 재실행됩니다. 이때 StockButton은 React.memo로 감싸져 있어 버튼 자체의 연산은 방어할 수 있겠지만, 그 바깥의 <li> 태그와 텍스트(상품명, 품절 여부)들은 렌더링될 때마다 불필요하게 다시 생성됩니다.
따라서 실무에서 이를 완벽하게 최적화하려면 map 내부의 <li> 요소 전체를 <ProductItem>이라는 별도의 단일 컴포넌트로 분리하고, 이 <ProductItem> 컴포넌트 자체를 React.memo로 감싸는 구조로 변경해야 합니다. 이를 통해 부모로부터 전파되는 간접적인 리렌더링 폭포를 완벽히 차단하는 견고한 최적화를 완성할 수 있습니다.
import React, { useContext, memo } from 'react';
import { ProductStateContext } from '../contexts/ProductContext';
import { StockButton } from './StockButton';
// 1. 개별 상품 항목을 독립된 컴포넌트로 분리하고 React.memo로 감싸기
const ProductItem = memo(({ item }) => {
return (
<li style={{ marginBottom: '10px' }}>
<span>
{item.name} - {item.isSoldOut ? '품절' : '판매중'}
</span>
<StockButton id={item.id} />
</li>
);
});
// 2. 부모 컴포넌트인 ProductList는 items 배열이 바뀌어 리렌더링 되더라도,
// 내용이 바뀌지 않은 다른 ProductItem들의 렌더링을 건너뛰게 됩니다.
export default function ProductList() {
const items = useContext(ProductStateContext);
return (
<ul>
{items.map((item) => (
<ProductItem key={item.id} item={item} />
))}
</ul>
);
}
여기에 더해 실무 적용과 깊이 있는 이해를 위한 내용도 함께 짚어보겠습니다. 먼저 실무 팁으로 무분별한 React.memo 사용을 주의해야 한다는 점을 말씀드리고 싶습니다. 이 함수는 컴포넌트가 다시 그려지기 전에 이전 속성과 새로운 속성이 같은지 얕은 비교를 수행하게 됩니다. 만약 렌더링 자체가 아주 가벼운 단순한 UI 컴포넌트라면 이 비교 연산에 드는 리소스가 렌더링 비용보다 오히려 더 클 수 있습니다. 따라서 무거운 연산이 포함된 컴포넌트나 렌더링 빈도가 매우 높은 리스트의 하위 항목에 선택적으로 적용하는 것이 올바른 실무 방식이며, 앞서 말씀드린 <ProductItem> 분리 구조 같은 케이스가 바로 그 적절한 예시입니다.
또한 Context와 React.memo가 만드는 시너지 측면에서도 접근해 볼 수 있습니다. 실무에서는 전역 상태가 변경될 때 발생하는 광범위한 렌더링 폭포를 제어하는 것이 성능 최적화의 핵심으로 꼽힙니다. 상태와 명령 채널을 분리하고, 명령이나 고정된 데이터만 사용하는 하위 컴포넌트(혹은 리스트 아이템)를 React.memo로 한 번 더 보호하는 패턴은 대규모 React 애플리케이션 설계 시 필수적으로 활용되는 견고한 아키텍처입니다.
더 깊은 학습에 도움이 될 만한 내용으로 참조 동등성의 함정에 대해서도 생각해 볼 필요가 있습니다. 컴포넌트가 속성을 비교할 때 숫자나 문자열 같은 원시 타입은 값이 같으면 동일하다고 판단하지만, 객체나 함수 같은 참조 타입은 메모리 주소가 다르면 값이 변했다고 판단하게 됩니다. 만약 하위 컴포넌트에 id라는 원시 타입 대신 부모에서 생성한 함수를 직접 속성으로 넘겨주었다면 부모가 렌더링될 때마다 함수가 새로 생성되어 방어막이 뚫리게 됩니다. 이러한 현상을 막기 위해 짝꿍처럼 쓰이는 훅이 바로 useCallback입니다.
import React, { useCallback, memo } from 'react';
const StockButton = memo(({ id, onClick }) => {
return <button onClick={() => onClick(id)}>재고 변경</button>;
});
export default function ParentComponent() {
// 부모가 리렌더링되더라도 함수 참조(메모리 주소)를 동일하게 유지하여
// 하위 컴포넌트의 React.memo 방어막이 뚫리지 않도록 보호합니다.
const handleStockChange = useCallback((id) => {
console.log(`${id}번 상품 재고 변경 로직`);
}, []); // 의존성 배열이 비어있어 최초 한 번만 함수 생성
return (
<StockButton id={1} onClick={handleStockChange} />
);
}
마지막으로 렌더링 건너뛰기 현상에 대한 개념도 중요합니다. React 공식 문서 및 동작 원리에서는 조건 충족이나 동일한 상태 업데이트로 인해 렌더링 작업이 취소되고 건너뛰어지는 현상을 'Bailout'이라고 부릅니다. 이 키워드로 React의 렌더링 최적화 로직을 추가 학습하시면 프레임워크의 내부 동작을 완벽히 이해하는 데 큰 도움이 될 것입니다. 학습에 유용한 참고가 되시길 바랍니다.
0
안녕하세요, 인프런 AI 인턴입니다. 이태관님.
StockButton 컴포넌트를 memo로 감싸는 것은 React의 성능 최적화를 위한 방법입니다. memo는 해당 컴포넌트의 props가 변경되지 않는 한 재렌더링을 방지하게 됩니다. 이는 불필요한 렌더링을 줄여 성능 향상을 도모합니다.
- memo의 역할:
memo는 React의 고급 기능으로서, 컴포넌트가 동일한 props로 여러 번 렌더링될 때, 반드시 변경이 필요한 상황이 아닌 한 그 작업을 피합니다. 결과적으로 컴포넌트 렌더링 비용을 줄일 수 있습니다.
- 이는 컴포넌트가 복잡하고 렌더링에 비용이 높은 경우에 특히 유용합니다.
한편, ProductList 컴포넌트가 정답 코드에 없다는 점은 독특합니다. 경우에 따라 특정 컴포넌트가 강의나 미션 내용에만 포함될 수 있습니다. 따라서 StockButton의 위치는 기존의 UI 구조나 프로젝트 요구 사항에 적절히 배치하면 됩니다.
관련된 자세한 설명과 예제를 보려면, React에서의 컴포넌트 최적화를 참고해 주세요.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
vercel new project 에 노출되지 않으면 어떻게 해야 할까요?
0
17
1
소스코드 어디서 다운받아요?
0
13
5
동영상 끊김 ( 섹션 2 )
0
15
2
supabase 다른 프로젝트 적용 관련 질문드려요.
0
21
1
시스템관리자가 앱을 차단했다고 뜹니다.
0
14
1
프로젝트 폴더 복사 후 사용 관련
0
19
2
기술스택 강의 관련해서 질문드려요.
0
22
2
강의 자료를 찾을 수 없습니다ㅠㅠ
0
22
2
서브에이전트 문의
0
22
2
노션 강의 화면과 실제 화면과 너무 달라서 수업 진행이 안 됩니다
0
29
1
파워셀에서 claude 코드의 버전확인이 않됩니다.
0
25
2
윈도우에서 설정화면이 다릅니다.클로드코드 환경변수 설정
0
23
2
React 와 Virtual DOM 의 이야기 영상 실행이 안됩니다.
0
19
1
깃허브 Publish 질문
0
29
2
클로드 코드 프로 사용자인데..
0
35
2
강의는 순서대로 들어야 할까요??
0
35
1
supabase 사용 관련.
0
32
2
상태(State) 가 "시간이 지남~" 에 대해 질문 있습니다.
0
24
2
문서 업데이트
0
37
2
useReducer와 커스텀훅
0
46
1
실습 가이드: 16강 에서 useMemo의 역할은 무엇인가요?
0
43
3
useReducer가 race condition을 해소하는 예시
0
99
1
useRef를 활용한 이전 상태 추적 시 발생하는 ESLint 에러(react-hooks/refs)에 대해 질문드립니다.
0
153
1
미션18
0
73
2

