Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
Tags
- react router dom
- react pattern
- 동적계획법
- 프로그래머스
- 그리디
- 자바스크립트
- tanstack query
- TypeScript
- JavaScript
- 리액트
- 리액트 라우터 돔
- tailwind
- reduxtoolkit
- 리액트 패턴
- 토이 프로젝트
- 코어자바스크립트
- 스택
- React
- 코딩테스트
- React Query
- 타입스크립트
- revalidatepath
- styled component
- 리덕스
- Supabase
- 프론트엔드
- Next.js
- 토이프로젝트
- Form
- 코테
Archives
- Today
- Total
느려도 한걸음씩
React Pattern - (2) render props 패턴 본문
Render Props 패턴
React 애플리케이션을 개발하다 보면 공통적인 UI 구조에 서로 다른 데이터를 넣어야 하는 상황을 자주 마주하게 됩니다.
그럴 때 유용하게 쓸 수 있는 패턴 중 하나가 바로 Render Props 패턴입니다.
이번 글에서는 실제 예제 코드를 통해 Render Props가 무엇인지, 그리고 적용 전후의 차이점과 장점을 살펴보겠습니다.
다음은 List 컴포넌트를 사용한 예제입니다. 이 컴포넌트는 products나 companies 같은 데이터를 받아 리스트를 렌더링합니다.
import { useState } from "react";
import { faker } from "@faker-js/faker";
import "./styles.css";
const products = Array.from({ length: 20 }, () => {
return {
productName: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: faker.commerce.price()
};
});
const companies = Array.from({ length: 15 }, () => {
return {
companyName: faker.company.name(),
phrase: faker.company.catchPhrase()
};
});
function ProductItem({ product }) {
return (
<li className="product">
<p className="product-name">{product.productName}</p>
<p className="product-price">${product.price}</p>
<p className="product-description">{product.description}</p>
</li>
);
}
function CompanyItem({ company, defaultVisibility }) {
const [isVisible, setIsVisisble] = useState(defaultVisibility);
return (
<li
className="company"
onMouseEnter={() => setIsVisisble(true)}
onMouseLeave={() => setIsVisisble(false)}
>
<p className="company-name">{company.companyName}</p>
{isVisible && (
<p className="company-phrase">
<strong>About:</strong> {company.phrase}
</p>
)}
</li>
);
}
function List({ title, items }) {
const [isOpen, setIsOpen] = useState(true);
const [isCollapsed, setIsCollapsed] = useState(false);
const displayItems = isCollapsed ? items.slice(0, 3) : items;
function toggleOpen() {
setIsOpen((isOpen) => !isOpen);
setIsCollapsed(false);
}
return (
<div className="list-container">
<div className="heading">
<h2>{title}</h2>
<button onClick={toggleOpen}>
{isOpen ? <span>∨</span> : <span>∧</span>}
</button>
</div>
{isOpen && (
<ul className="list">
{displayItems.map((product) => (
<ProductItem key={product.productName} product={product} />
))}
</ul>
)}
<button onClick={() => setIsCollapsed((isCollapsed) => !isCollapsed)}>
{isCollapsed ? `Show all ${items.length}` : "Show less"}
</button>
</div>
);
}
export default function App() {
return (
<div>
<h1>Render Props Demo</h1>
<div className="col-2">
<List title="Products" items={products} />
</div>
</div>
);
}
이 구조는 ProductItem에 대해서만 하드코딩되어 있기 때문에,
다른 형태의 데이터를 렌더링하려면 컴포넌트를 하나 더 만들거나 조건 분기(if문)를 넣어야 합니다.
이로 인해 다음과 같은 문제가 발생합니다:
- List 컴포넌트의 재사용성이 낮음
- 하드코딩된 구조로 인해 UI 로직이 고정됨
- 다른 데이터(예: companies)를 다루려면 별도의 리스트 컴포넌트를 만들어야 함 → 중복 증가
✅ Render Props란?
Render Props는 말 그대로 렌더링을 위한 prop(함수)을 컴포넌트에 전달하는 패턴입니다.
즉, 어떤 컴포넌트 내부에서 렌더링할 UI를 직접 정하지 않고,
외부에서 전달받은 함수를 이용해 유연하게 렌더링하는 방식입니다.
🔁 Render Props 패턴으로 리팩토링
List 컴포넌트를 다음과 같이 수정해 봅시다
function List({ title, items, render }) {
const [isOpen, setIsOpen] = useState(true);
const [isCollapsed, setIsCollapsed] = useState(false);
const displayItems = isCollapsed ? items.slice(0, 3) : items;
function toggleOpen() {
setIsOpen((isOpen) => !isOpen);
setIsCollapsed(false);
}
return (
<div className="list-container">
<div className="heading">
<h2>{title}</h2>
<button onClick={toggleOpen}>
{isOpen ? <span>∨</span> : <span>∧</span>}
</button>
</div>
{isOpen && <ul className="list">{displayItems.map(render)}</ul>}
<button onClick={() => setIsCollapsed((isCollapsed) => !isCollapsed)}>
{isCollapsed ? `Show all ${items.length}` : "Show less"}
</button>
</div>
);
}
이제 List는 어떤 아이템을 어떻게 렌더링할지 신경 쓰지 않고,
그 책임을 props로 전달받은 render 함수에 위임합니다.
사용자는 아래처럼 자유롭게 구성할 수 있어요
<List
title="Products"
items={products}
render={(product) => (
<ProductItem key={product.productName} product={product} />
)}
/>
<List
title="Companies"
items={companies}
render={(company) => (
<CompanyItem key={company.companyName} company={company} defaultVisibility={false} />
)}
/>
🌟 Render Props 패턴의 장점
1. 재사용성과 유연성 증가
- List는 이제 어떤 데이터든, 어떤 컴포넌트든 렌더링 가능
- 하나의 컴포넌트를 다양한 상황에 맞춰 사용할 수 있음
2. 로직과 UI 분리
- 리스트 관리(열림/닫힘, 접기 등)는 List가 담당
- 렌더링할 컴포넌트는 외부에서 결정 → 책임 분리
3. 중복 제거
- ProductList, CompanyList 같은 컴포넌트를 별도로 만들 필요가 없음
- 동일한 로직을 재사용하며 UI는 다르게 구성 가능
전체코드(render props 패턴 적용)
import { useState } from "react";
import { faker } from "@faker-js/faker";
import "./styles.css";
const products = Array.from({ length: 20 }, () => {
return {
productName: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: faker.commerce.price()
};
});
const companies = Array.from({ length: 15 }, () => {
return {
companyName: faker.company.name(),
phrase: faker.company.catchPhrase()
};
});
function ProductItem({ product }) {
return (
<li className="product">
<p className="product-name">{product.productName}</p>
<p className="product-price">${product.price}</p>
<p className="product-description">{product.description}</p>
</li>
);
}
function CompanyItem({ company, defaultVisibility }) {
const [isVisible, setIsVisisble] = useState(defaultVisibility);
return (
<li
className="company"
onMouseEnter={() => setIsVisisble(true)}
onMouseLeave={() => setIsVisisble(false)}
>
<p className="company-name">{company.companyName}</p>
{isVisible && (
<p className="company-phrase">
<strong>About:</strong> {company.phrase}
</p>
)}
</li>
);
}
function List({ title, items, render }) {
const [isOpen, setIsOpen] = useState(true);
const [isCollapsed, setIsCollapsed] = useState(false);
const displayItems = isCollapsed ? items.slice(0, 3) : items;
function toggleOpen() {
setIsOpen((isOpen) => !isOpen);
setIsCollapsed(false);
}
return (
<div className="list-container">
<div className="heading">
<h2>{title}</h2>
<button onClick={toggleOpen}>
{isOpen ? <span>∨</span> : <span>∧</span>}
</button>
</div>
{isOpen && <ul className="list">{displayItems.map(render)}</ul>}
<button onClick={() => setIsCollapsed((isCollapsed) => !isCollapsed)}>
{isCollapsed ? `Show all ${items.length}` : "Show less"}
</button>
</div>
);
}
export default function App() {
return (
<div>
<h1>Render Props Demo</h1>
<div className="col-2">
<List
title="Products"
items={products}
render={(product) => (
<ProductItem key={product.productName} product={product} />
)}
/>
<List
title="Companies"
items={companies}
render={(company) => (
<CompanyItem
key={company.companyName}
company={company}
defaultVisibility={false}
/>
)}
/>
</div>
</div>
);
}
'FE develop > React Pattern' 카테고리의 다른 글
React Pattern - (4) Compound Component 패턴 (0) | 2025.04.11 |
---|---|
React Pattern - (3) HOC(Higher-Order Component) 패턴 (0) | 2025.04.11 |
React Pattern - (1) React Pattern을 배워야 하는 이유 (0) | 2025.04.11 |