React: Component Composition and Reuse
Last updated: 2026-08-26
Combining components is like building with Legos—not every piece needs to be unique. Good component design means that the basic building blocks (subcomponents) are flexible and can be assembled in various ways to create any shape.
1. What You'll Learn
- Composition vs. Inheritance
- Children slots and multi-slot mode
- Principles of Component Decomposition (Single Responsibility)
- Render Props pattern
- Introduction to Higher-Order Components (HOCs)
2. The Story of a Design System
(1) Pain Point: Reinventing the wheel on every page
Charlie is the company's front-end architect. He noticed that every page was reinventing the wheel:
// Page A:Custom Pop-up Window
<div style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>
<div style={{ backgroundColor: 'white', padding: '24px', borderRadius: '8px' }}>
<h2>Confirm Deletion</h2>
<p>Are you sure you want to delete this record??</p>
<button>Cancel</button>
<button>Confirm</button>
</div>
</div>
// Page B:Another pop-up,But the style is slightly different
<div style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.3)',
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>
<div style={{ backgroundColor: 'white', padding: '32px', borderRadius: '12px' }}>
<h2>Edit User</h2>
<input placeholder="Username" />
<input placeholder="Email" />
<button>Save</button>
<button>Cancel</button>
</div>
</div>
Issue: The styles for the mask layer and the container are duplicated across two pages. If you want to change the size of the pop-up’s rounded corners, you have to edit 20 pages.
(2) Solution to the Combinatorial Model
// General Popup Component(Responsible only for structure and style,Not interested in the content)
function Modal({ isOpen, onClose, title, children }) {
if (!isOpen) return null
return (
<div style={overlayStyle} onClick={onClose}>
<div style={modalStyle} onClick={e => e.stopPropagation()}>
{title && <h2 style={{ margin: '0 0 16px 0' }}>{title}</h2>}
{children} {/* The content is determined by the user. */}
</div>
</div>
)
}
// Page A:Confirm Deletion
<Modal isOpen={showDelete} onClose={() => setShowDelete(false)} title="Confirm Deletion">
<p>Are you sure you want to delete this record??This action cannot be undone.。</p>
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<button onClick={() => setShowDelete(false)}>Cancel</button>
<button style={{ backgroundColor: '#ff4d4f', color: 'white' }}>Confirm Deletion</button>
</div>
</Modal>
// Page B:Edit User
<Modal isOpen={showEdit} onClose={() => setShowEdit(false)} title="Edit User">
<input placeholder="Username" />
<input placeholder="Email" />
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '16px' }}>
<button>Save</button>
<button onClick={() => setShowEdit(false)}>Cancel</button>
</div>
</Modal>
Benefits: Centralized management of pop-up styles (change one place, change all); content is entirely determined by the user; a single Modal component is reused across 20 pages.
graph TB
subgraph "Combination Mode"
A[Modal Components] --> B[Overlay]
A --> C[Container modal]
C --> D[Title title]
C --> E[**children slot**]
E --> F[The content is determined by the parent component]
end
style E fill:#1890ff,color:#fff
3. children slot
| Slot Mode | Syntax | Use Cases |
|---|---|---|
| Single Slot | {children} |
Simple Container (Card, Layout) |
| Multi-slot | {props.header} {props.footer} |
Multi-region layout (Page, Dialog) |
| renderProps | {props.renderItem(item)} |
Custom Rendering of Lists/Tables |
| Function Subcomponent | {props.children(data)} |
Data Propagation + Custom Rendering |
(1) Basic Usage
children is a special Prop that represents the content between the component's tags.
// Component Definition
function Card({ title, children }) {
return (
<div style={{
border: '1px solid #f0f0f0',
borderRadius: '8px',
padding: '16px',
marginBottom: '16px'
}}>
{title && <h3 style={{ margin: '0 0 12px 0', color: '#333' }}>{title}</h3>}
<div>{children}</div>
</div>
)
}
// How to Use 1:A single element
<Card title="Announcement">
<p>The system will go live tonight 22:00 Perform maintenance</p>
</Card>
// How to Use 2:Multiple elements
<Card title="User Information">
<p>Name:Alice</p>
<p>Characters:Administrator</p>
<p>Email:alice@example.com</p>
</Card>
// How to Use 3:and even other components
<Card title="Statistical Overview">
<StatCard label="Number of users" value={1234} />
<StatCard label="Number of Orders" value={567} />
</Card>
(2) Multi-slot mode
If a component requires multiple slot positions, you can use named props (such as header, footer, main):
function PageLayout({ header, sidebar, children, footer }) {
return (
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
{/* Header Socket */}
<header style={{ backgroundColor: '#001529', color: 'white', padding: '16px' }}>
{header}
</header>
<div style={{ display: 'flex', flex: 1 }}>
{/* Sidebar Slot */}
<aside style={{ width: '240px', backgroundColor: '#f5f5f5', padding: '16px' }}>
{sidebar}
</aside>
{/* Main Content Slot(children) */}
<main style={{ flex: 1, padding: '24px' }}>
{children}
</main>
</div>
{/* Bottom Slot */}
<footer style={{ backgroundColor: '#f0f0f0', padding: '12px', textAlign: 'center' }}>
{footer}
</footer>
</div>
)
}
// Usage
<PageLayout
header={<><h1>Management System</h1><nav>Navigation Menu</nav></>}
sidebar={<><h3>List of Features</h3><ul><li>User Management</li><li>Order Management</li></ul></>}
footer={<p>© 2026 web-tutorial.com</p>}
>
<h2>Welcome back,Administrator</h2>
<p>Today's Orders:128 orders</p>
<p>New Users:23 users</p>
</PageLayout>
▶ Example: Generic List Component
Output:
Button: Add Item. Displays: ") : ( data.map((item, index) => ("
// ============================================
// Example:Generic List Component(Multi-slot + children)
// ============================================
function DataList({ data, renderItem, header, emptyMessage = 'No data available' }) {
return (
<div style={{ border: '1px solid #f0f0f0', borderRadius: '8px' }}>
{/* Header Socket */}
{header && <div style={{ padding: '12px 16px', borderBottom: '1px solid #f0f0f0', backgroundColor: '#fafafa' }}>{header}</div>}
{/* List Contents */}
{data.length === 0 ? (
<div style={{ padding: '40px', textAlign: 'center', color: '#999' }}>{emptyMessage}</div>
) : (
data.map((item, index) => (
<div key={item.id} style={{
padding: '12px 16px',
borderBottom: index < data.length - 1 ? '1px solid #f0f0f0' : 'none',
transition: 'background-color 0.2s'
}}
onMouseEnter={e => e.currentTarget.style.backgroundColor = '#fafafa'}
onMouseLeave={e => e.currentTarget.style.backgroundColor = 'transparent'}
>
{renderItem(item)}
</div>
))
)}
{/* children You can add additional action buttons */}
{data.length > 0 && <div style={{ padding: '12px 16px', borderTop: '1px solid #f0f0f0' }}>{data.length} records</div>}
</div>
)
}
// Examples of Use 1:User List
<DataList
data={users}
header={<h3 style={{ margin: 0 }}>User List</h3>}
renderItem={user => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{user.name}</span>
<span style={{ color: '#999' }}>{user.role}</span>
</div>
)}
/>
// Examples of Use 2:Product List
<DataList
data={products}
header={<h3 style={{ margin: 0 }}>Product List <button>Add Item</button></h3>}
renderItem={product => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{product.name}</span>
<span style={{ color: '#ff4d4f' }}>${product.price}</span>
</div>
)}
emptyMessage="No products have been listed yet."
/>
Output:
Reusable list: header slot, custom renderItem, empty state, record count footer. Hover highlight on items.
4. Principles of Component Decomposition
Good components are like good functions—they have a single responsibility, are reusable, and are easy to test.
| Principle | Explanation | Counterexample |
|---|---|---|
| Single Responsibility | A component does only one thing | A component both displays user information and processes data exports |
| Minimize Interfaces | The fewer props, the better (≤5) | A component has 15 props |
| Data Flows Downward | Data flows unidirectionally from parent to child | Child components directly modify global state |
| No side effects | Identical props render identical results | Modify the DOM or call APIs directly within the component |
▶ Example: Before splitting vs. After splitting
Output:
Displays: ") : ( data.map((item, index) => (". Button: Add Item
// ============================================
// Example:Component Breakdown——from "Giant Components"to "Widget"
// ============================================
// ---- Before the split:One component does it all ----
function GiantUserPage({ userId }) {
const [user, setUser] = React.useState(null)
const [loading, setLoading] = React.useState(true)
const [editing, setEditing] = React.useState(false)
const [name, setName] = React.useState('')
const [email, setEmail] = React.useState('')
const [posts, setPosts] = React.useState([])
// ... 100 Multiple lines of logic mixed together
return (
<div>
{/* User Information、Edit Form、The list of articles is all crammed together */}
</div>
)
}
// ---- After the split:Each to their own role ----
function UserPage({ userId }) {
return (
<div>
<UserProfile userId={userId} /> {/* Responsible only for displaying user information */}
<UserEditForm userId={userId} /> {/* Responsible only for editing user content */}
<UserPosts userId={userId} /> {/* Responsible only for displaying the list of articles */}
</div>
)
}
// Each subcomponent can be developed independently.、Test、Reuse
function UserProfile({ userId }) { /* ... */ }
function UserEditForm({ userId }) { /* ... */ }
function UserPosts({ userId }) { /* ... */ }
Output:
Before: 100+ line monolith. After: <UserPage> composes <UserProfile>, <UserEditForm>, <UserPosts> — each independent, testable, reusable
(1) Rules of Thumb for Splitting
If a component exceeds 150 lines
A function that exceeds 30 lines
One render Return more than 10 JSX Element
Props More than 5
→ Consider a split!
5. Render Props pattern
Render Props is another composition pattern—it allows a parent component to control what a child component renders through a function prop.
// ============================================
// Example:Render Props——Mouse Position Tracker
// ============================================
// Child component:Responsible only for"Track Mouse Position",Don't care"How to display"
function MouseTracker({ render }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 })
function handleMouseMove(e) {
setPosition({ x: e.clientX, y: e.clientY })
}
return (
<div style={{ height: '300px', border: '1px solid #ddd' }} onMouseMove={handleMouseMove}>
{/* Let the parent component determine how to render the mouse position */}
{render(position)}
</div>
)
}
// Parent Component:Decision"How to display"
function App() {
return (
<div>
<h2>Render Props Example</h2>
{/* Usage 1:Display Coordinates */}
<MouseTracker render={({ x, y }) => (
<div style={{ padding: '20px' }}>
<p>Mouse Position:({x}, {y})</p>
</div>
)} />
{/* Usage 2:Display Coordinate Indicator */}
<MouseTracker render={({ x, y }) => (
<div style={{
position: 'absolute', left: x, top: y,
width: '20px', height: '20px',
backgroundColor: '#1890ff', borderRadius: '50%',
transform: 'translate(-50%, -50%)',
pointerEvents: 'none'
}} />
)} />
</div>
)
}
Note: Since the introduction of React Hooks, most use cases for Render Props can be replaced with custom hooks (which are more concise). Render Props are commonly found in legacy code.
6. Composition vs. Inheritance
The React team explicitly states: Don't use inheritance. Use composition.
// ❌ Inheritance Patterns(Don't write it that way.)
class BaseButton extends React.Component {
// ...Overriding Methods After Inheritance in a Subclass,React Not recommended
}
class PrimaryButton extends BaseButton { }
// ✅ Combination Mode(The Correct Way to Do It)
function Button({ variant = 'default', children, ...props }) {
const styles = {
primary: { backgroundColor: '#1890ff', color: 'white' },
danger: { backgroundColor: '#ff4d4f', color: 'white' },
default: { backgroundColor: '#f0f0f0', color: '#333' }
}
return <button style={styles[variant]} {...props}>{children}</button>
}
// Through Props Control Variants,rather than through inheritance
<Button variant="primary">Main Buttons</Button>
<Button variant="danger">Danger Button</Button>
<Button variant="default">Default Button</Button>
| Dimension | Combination (Recommended) | Inheritance (Not Recommended) |
|---|---|---|
| Flexibility | High—Freely combinable via props and children | Low—Fixed inheritance chain |
| Testability | High—Each component can be tested independently | Low—Depends on the parent classe's implementation |
| Type Safety | Good—Props are explicitly typed | Poor—Overriding methods is prone to errors |
7. Complete Example: A Reusable Dashboard Card System
// ============================================
// Complete Example:Dashboard Card System
// Features:Combination Mode + children + Props Integrated Use of
// ============================================
// ---- 1. Basic Components:Card Holder ----
function DashboardCard({ title, icon, action, children, width = '300px' }) {
return (
<div style={{
border: '1px solid #f0f0f0',
borderRadius: '8px',
width,
backgroundColor: 'white',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)'
}}>
{/* Card Header */}
<div style={{
display: 'flex', justifyContent: 'space-between',
alignItems: 'center', padding: '16px',
borderBottom: '1px solid #f0f0f0'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{icon && <span>{icon}</span>}
<h3 style={{ margin: 0, fontSize: '16px' }}>{title}</h3>
</div>
{action && <div>{action}</div>}
</div>
{/* Card Content Slot */}
<div style={{ padding: '16px' }}>
{children}
</div>
</div>
)
}
// ---- 2. Statistics Card ----
function StatCard({ label, value, trend, color = '#1890ff' }) {
return (
<div style={{ textAlign: 'center', padding: '8px 0' }}>
<p style={{ color: '#666', fontSize: '14px', margin: '0 0 8px 0' }}>{label}</p>
<p style={{ fontSize: '32px', fontWeight: 'bold', color, margin: '0 0 8px 0' }}>{value}</p>
{trend !== undefined && (
<p style={{
color: trend >= 0 ? '#52c41a' : '#ff4d4f',
fontSize: '14px', margin: 0
}}>
{trend >= 0 ? '↑' : '↓'} {Math.abs(trend)}%
</p>
)}
</div>
)
}
// ---- 3. List Cards ----
function ListCard({ title, icon, items, renderItem, action, emptyText = 'No data available' }) {
return (
<DashboardCard title={title} icon={icon} action={action}>
{items.length === 0 ? (
<p style={{ textAlign: 'center', color: '#999', padding: '20px' }}>{emptyText}</p>
) : (
items.map((item, index) => (
<div key={item.id} style={{
padding: '8px 0',
borderBottom: index < items.length - 1 ? '1px solid #f5f5f5' : 'none'
}}>
{renderItem(item)}
</div>
))
)}
</DashboardCard>
)
}
// ---- 4. Usage:Complete Dashboard ----
function Dashboard() {
const stats = [
{ id: 'users', label: 'Total Users', value: '12,846', trend: 12 },
{ id: 'orders', label: 'Today's Orders', value: '328', trend: -3 },
{ id: 'revenue', label: 'Monthly Income', value: '$89,200', trend: 25 },
{ id: 'visits', label: 'Visits Today', value: '4,621', trend: 8 }
]
const recentOrders = [
{ id: 1, customer: 'Alice', product: 'React Tutorial', amount: 89, status: 'Completed' },
{ id: 2, customer: 'Bob', product: 'TypeScript Getting Started', amount: 59, status: 'Processing...' },
{ id: 3, customer: 'Charlie', product: 'Node.js Real-World Experience', amount: 129, status: 'Completed' }
]
return (
<div>
<h2>📊 Management Dashboard</h2>
{/* Statistical Card Rows(Grid Layout) */}
<div style={{ display: 'flex', gap: '16px', marginBottom: '24px', flexWrap: 'wrap' }}>
<DashboardCard title="Data Overview" icon="📈" width="100%">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
{stats.map(s => (
<StatCard key={s.id} {...s} />
))}
</div>
</DashboardCard>
</div>
{/* List of Recent Orders */}
<div style={{ display: 'flex', gap: '16px' }}>
<ListCard
title="Recent Orders"
icon="📋"
items={recentOrders}
action={<button style={btnStyle}>View All</button>}
renderItem={order => (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<p style={{ margin: 0, fontWeight: 'bold' }}>{order.customer}</p>
<p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '13px' }}>{order.product}</p>
</div>
<div style={{ textAlign: 'right' }}>
<p style={{ margin: 0, color: '#ff4d4f' }}>${order.amount}</p>
<span style={{
fontSize: '12px', padding: '2px 6px', borderRadius: '4px',
backgroundColor: order.status === 'Completed' ? '#f6ffed' : '#fff7e6',
color: order.status === 'Completed' ? '#52c41a' : '#faad14'
}}>
{order.status}
</span>
</div>
</div>
)}
/>
{/* Custom Cards:Use directly children */}
<DashboardCard title="Quick Actions" icon="⚡" width="300px">
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<button style={actionBtnStyle}>📝 Create a New Order</button>
<button style={actionBtnStyle}>👤 Add User</button>
<button style={actionBtnStyle}>📊 Generate Reports</button>
<button style={actionBtnStyle}>⚙️ System Settings</button>
</div>
</DashboardCard>
</div>
</div>
)
}
const btnStyle = {
padding: '4px 12px', border: '1px solid #d9d9d9',
borderRadius: '4px', backgroundColor: 'white',
cursor: 'pointer', fontSize: '12px'
}
const actionBtnStyle = {
width: '100%', padding: '10px', border: 'none',
borderRadius: '4px', backgroundColor: '#f5f5f5',
cursor: 'pointer', textAlign: 'left', fontSize: '14px',
transition: 'background-color 0.2s'
}
// Hover effects are enabled via onMouseEnter/Leave Add,Omitted here
Expected Output: A complete Dashboard page featuring a data overview card (with 4 metrics) at the top, a list of recent orders on the lower left, and a group of quick-action buttons on the lower right. All cards are built using the composition pattern.
▶ Example 3: Render Props—Sortable Tables
Output:
Sortable table: click column headers to sort asc/desc. Data rows reorder with sort direction indicator
function SortableTable({ data, columns, renderCell }) {
const [sortKey, setSortKey] = useState(null)
const [sortDir, setSortDir] = useState('asc')
const sorted = useMemo(() => {
if (!sortKey) return data
return [...data].sort((a, b) => {
const diff = a[sortKey] > b[sortKey] ? 1 : a[sortKey] < b[sortKey] ? -1 : 0
return sortDir === 'asc' ? diff : -diff
})
}, [data, sortKey, sortDir])
function handleSort(key) {
if (sortKey === key) setSortDir(d => d === 'asc' ? 'desc' : 'asc')
else { setSortKey(key); setSortDir('asc') }
}
return (
<table border={1} cellPadding={8} style={{ borderCollapse: 'collapse', width: '100%' }}>
<thead>
<tr>
{columns.map(col => (
<th key={col.key} onClick={() => handleSort(col.key)} style={{ cursor: 'pointer' }}>
{col.label} {sortKey === col.key ? (sortDir === 'asc' ? '↑' : '↓') : ''}
</th>
))}
</tr>
</thead>
<tbody>
{sorted.map((row, i) => (
<tr key={i}>{columns.map(col => <td key={col.key}>{renderCell(row, col.key)}</td>)}</tr>
))}
</tbody>
</table>
)
}
function App() {
const users = [
{ name: 'Alice', score: 92, role: 'Admin' },
{ name: 'Bob', score: 85, role: 'Editor' },
{ name: 'Charlie', score: 78, role: 'Viewer' },
]
const cols = [{ key: 'name', label: 'Name' }, { key: 'score', label: 'Score' }, { key: 'role', label: 'Role' }]
return (
<div style={{ maxWidth: 500, margin: '0 auto' }}>
<h3>Sortable Users</h3>
<SortableTable data={users} columns={cols}
renderCell={(row, key) => key === 'score' ? `${row[key]} pts` : row[key]} />
</div>
)
}
Output:
Sortable table: Alice 92pts, Bob 85pts, Charlie 78pts. Click headers to sort ↑/↓. Scores shown as "X pts".
▶ Example 4: Layout Combinations—Multi-Region Layouts
Output:
Layout with Header, Sidebar, Main, Footer regions. Composition pattern for flexible page structure
function AppLayout({ sidebar, header, content, footer }) {
return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
{sidebar && <aside style={{ width: 200, background: '#f5f5f5', padding: 16 }}>{sidebar}</aside>}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
{header && <header style={{ padding: '12px 16px', background: '#1890ff', color: 'white' }}>{header}</header>}
<main style={{ flex: 1, padding: 16 }}>{content}</main>
{footer && <footer style={{ padding: '8px 16px', background: '#fafafa', fontSize: 12, color: '#999' }}>{footer}</footer>}
</div>
</div>
)
}
function Dashboard() {
return (
<AppLayout
sidebar={<nav><p>Menu</p><ul><li>Home</li><li>Settings</li></ul></nav>}
header={<h2 style={{ margin: 0 }}>Dashboard</h2>}
content={<p>Welcome back, Alice! You have 5 new tasks.</p>}
footer={<span>© 2026 SaaS App</span>}
/>
)
}
Output:
Layout with Header, Sidebar, Main, Footer regions. Composition pattern for flexible page structure
▶ Example 5: Composition vs. Inheritance—Popup Component
Output:
Subheading: "Dashboard". Displays: "Menu". List: Home, Settings
function Modal({ isOpen, onClose, title, children, footer }) {
if (!isOpen) return null
return (
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}
onClick={onClose}>
<div style={{ background: 'white', borderRadius: 8, padding: 24, minWidth: 320, maxWidth: 500 }}
onClick={e => e.stopPropagation()}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<h3 style={{ margin: 0 }}>{title}</h3>
<button onClick={onClose} style={{ border: 'none', background: 'none', cursor: 'pointer', fontSize: 18 }}>✕</button>
</div>
<div style={{ marginBottom: 16 }}>{children}</div>
{footer && <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>{footer}</div>}
</div>
</div>
)
}
function ConfirmModal({ isOpen, onConfirm, onCancel, message }) {
return (
<Modal isOpen={isOpen} onClose={onCancel} title="Confirm"
footer={<>
<button onClick={onCancel} style={{ padding: '6px 16px', cursor: 'pointer' }}>Cancel</button>
<button onClick={onConfirm} style={{ padding: '6px 16px', background: '#ff4d4f', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>Confirm</button>
</>}>
<p>{message}</p>
</Modal>
)
}
function FormModal({ isOpen, onClose }) {
const [value, setValue] = useState('')
return (
<Modal isOpen={isOpen} onClose={onClose} title="New Item"
footer={<button onClick={() => { alert(value); onClose() }} style={{ padding: '6px 16px', background: '#1890ff', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>Submit</button>}>
<input value={value} onChange={e => setValue(e.target.value)} placeholder="Enter name" style={{ width: '100%', padding: 8, borderRadius: 4 }} />
</Modal>
)
}
Output:
Modal system: ConfirmModal (Cancel/Confirm, red confirm), FormModal (text input + Submit). Click overlay → closes. Composition over inheritance.
❓ FAQ
children, and when should you use Props?children is used for container components with "variable content areas" (such as modals, cards, and layouts). Props is used for components with "configurable content" (such as button colors and title text). Simply put: use children for structural elements and Props for configuration.UserForm component has 12 props, break it down into three child components: UserBasicInfo (name, email, phone number), UserAddress (province, city, district, detailed address), and UserSettings (role, permissions).useXxx() a single line of code, resulting in flatter, more readable code. Render Props only have an advantage when the reused logic needs to “render a piece of UI” (such as when a tooltip component needs to know the position of its child elements).📖 Summary
- Composition is better than inheritance—In React, we use composition 100% of the time and never use inheritance.
childrenis a special prop that implements the "slot" pattern, allowing components to be nested flexibly- Multi-slot mode can be used with named props (header/footer/sidebar) to implement complex layouts
- Component decomposition follows the Single Responsibility Principle: a component does only one thing
- Render Props pass rendering functions; Hooks are a better alternative.
📝 Exercises
- Basic Problem (Difficulty ⭐): Create a
Badgecomponent that acceptscount(a number) andchildren(content), and displays a red superscript number in the upper-right corner. - Advanced Exercise (Difficulty ⭐⭐): Create a
Tabscomponent and use the Composite Pattern to implement label switching:<Tabs><TabPanel label="Tab1">inside content 1</TabPanel><TabPanel label="Tab2">inside content 2</TabPanel></Tabs>. - Challenge (Difficulty: ⭐⭐⭐): Create a
Tablecomponent using the Render Props pattern:<Table data={users} columns={[{ title: 'Name', render: u => u.name }, { title: 'Age', render: u => u.age }]} />, and support sorting and zebra highlighting.