Prop drilling a problémy 🔄
V této části se podíváme na problém prop drilling - situaci, kdy musíme předávat props přes mnoho úrovní komponent, což vede k neudržitelnému kódu.
🎯 Co je prop drilling?
Prop drilling je situace, kdy data musí projít přes několik úrovní komponent, které je samy nepoužívají, pouze je předávají dál.
Problém:
// App.jsx - má data
const App = () => {
const [user, setUser] = useState({ name: 'Jan', role: 'admin' });
return <Dashboard user={user} onUserUpdate={setUser} />;
};
// Dashboard.jsx - nepoužívá user, jen předává
const Dashboard = ({ user, onUserUpdate }) => {
return (
<div>
<Header user={user} onUserUpdate={onUserUpdate} />
<MainContent user={user} onUserUpdate={onUserUpdate} />
</div>
);
};
// Header.jsx - nepoužívá user, jen předává
const Header = ({ user, onUserUpdate }) => {
return (
<div>
<Navigation user={user} onUserUpdate={onUserUpdate} />
<UserProfile user={user} onUserUpdate={onUserUpdate} />
</div>
);
};
// UserProfile.jsx - konečně používá user!
const UserProfile = ({ user, onUserUpdate }) => {
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => onUserUpdate({ ...user, name: 'Nové jméno' })}>
Změnit jméno
</button>
</div>
);
};
🚨 Problémy s prop drilling
1. Neudržitelný kód
- Mnoho props - komponenty mají desítky props
- Těžká údržba - změna v jedné komponentě ovlivní všechny
- Špatná čitelnost - není jasné, které props se skutečně používají
2. Těsné spojení (Tight Coupling)
- Komponenty jsou závislé na struktuře nadřazených komponent
- Těžké testování - musíme mockovat všechny props
- Špatná reusability - komponenty nelze snadno přesunout
3. Performance problémy
- Zbytečné re-rendery - změna props způsobí re-render všech komponent
- Memory overhead - props se předávají přes všechny úrovně
🎨 Praktický příklad - Korporátní struktura
Problémová situace:
// App.jsx
const App = () => {
const [employees, setEmployees] = useState([]);
const [selectedEmployee, setSelectedEmployee] = useState(null);
const [filter, setFilter] = useState('all');
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
return (
<div>
<CompanyHeader
employees={employees}
selectedEmployee={selectedEmployee}
filter={filter}
theme={theme}
notifications={notifications}
onEmployeesChange={setEmployees}
onEmployeeSelect={setSelectedEmployee}
onFilterChange={setFilter}
onThemeChange={setTheme}
onNotificationsChange={setNotifications}
/>
</div>
);
};
// CompanyHeader.jsx
const CompanyHeader = ({
employees, selectedEmployee, filter, theme, notifications,
onEmployeesChange, onEmployeeSelect, onFilterChange, onThemeChange, onNotificationsChange
}) => {
return (
<div>
<Navigation
employees={employees}
selectedEmployee={selectedEmployee}
filter={filter}
theme={theme}
notifications={notifications}
onEmployeesChange={onEmployeesChange}
onEmployeeSelect={onEmployeeSelect}
onFilterChange={onFilterChange}
onThemeChange={onThemeChange}
onNotificationsChange={onNotificationsChange}
/>
<MainContent
employees={employees}
selectedEmployee={selectedEmployee}
filter={filter}
theme={theme}
notifications={notifications}
onEmployeesChange={onEmployeesChange}
onEmployeeSelect={onEmployeeSelect}
onFilterChange={onFilterChange}
onThemeChange={onThemeChange}
onNotificationsChange={onNotificationsChange}
/>
</div>
);
};
// Navigation.jsx
const Navigation = ({
employees, selectedEmployee, filter, theme, notifications,
onEmployeesChange, onEmployeeSelect, onFilterChange, onThemeChange, onNotificationsChange
}) => {
return (
<nav>
<DepartmentFilter
employees={employees}
filter={filter}
onFilterChange={onFilterChange}
/>
<ThemeToggle
theme={theme}
onThemeChange={onThemeChange}
/>
<NotificationBell
notifications={notifications}
onNotificationsChange={onNotificationsChange}
/>
</nav>
);
};
// DepartmentFilter.jsx - konečně používá filter!
const DepartmentFilter = ({ filter, onFilterChange }) => {
return (
<select value={filter} onChange={(e) => onFilterChange(e.target.value)}>
<option value="all">Všechna oddělení</option>
<option value="IT">IT</option>
<option value="Design">Design</option>
</select>
);
};
🔍 Identifikace prop drilling
Znaky prop drilling:
- Komponenta má více než 5-7 props
- Props se předávají přes 3+ úrovně
- Komponenta nepoužívá většinu svých props
- Props se předávají jen proto, aby se dostaly do hlubší komponenty
Příklad identifikace:
// ❌ Prop drilling - komponenta má 10 props, používá jen 2
const EmployeeCard = ({
employee, // ✅ používá
onSelect, // ✅ používá
theme, // ❌ nepoužívá, předává dál
filter, // ❌ nepoužívá, předává dál
notifications, // ❌ nepoužívá, předává dál
user, // ❌ nepoužívá, předává dál
settings, // ❌ nepoužívá, předává dál
onThemeChange, // ❌ nepoužívá, předává dál
onFilterChange, // ❌ nepoužívá, předává dál
onNotificationAdd // ❌ nepoužívá, předává dál
}) => {
return (
<div onClick={() => onSelect(employee)}>
<h3>{employee.name}</h3>
<p>{employee.position}</p>
</div>
);
};
🛠️ Řešení prop drilling
1. Context API (nejčastější řešení)
// contexts/AppContext.jsx
import React, { createContext, useContext, useState } from 'react';
const AppContext = createContext();
export const AppProvider = ({ children }) => {
const [employees, setEmployees] = useState([]);
const [selectedEmployee, setSelectedEmployee] = useState(null);
const [filter, setFilter] = useState('all');
const [theme, setTheme] = useState('light');
const value = {
employees,
setEmployees,
selectedEmployee,
setSelectedEmployee,
filter,
setFilter,
theme,
setTheme
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
};
export const useApp = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useApp musí být použit uvnitř AppProvider');
}
return context;
};
2. Komponenta composition
// Místo předávání props přes komponenty
const App = () => {
return (
<AppProvider>
<CompanyHeader>
<Navigation>
<DepartmentFilter />
<ThemeToggle />
</Navigation>
<MainContent>
<EmployeeList />
</MainContent>
</CompanyHeader>
</AppProvider>
);
};
3. Custom hooks
// hooks/useEmployees.js
import { useState, useEffect } from 'react';
import apiService from '../services/api';
export const useEmployees = () => {
const [employees, setEmployees] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
loadEmployees();
}, []);
const loadEmployees = async () => {
try {
setLoading(true);
const data = await apiService.fetchEmployees();
setEmployees(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const addEmployee = async (employee) => {
try {
const newEmployee = await apiService.createEmployee(employee);
setEmployees(prev => [...prev, newEmployee]);
} catch (err) {
setError(err.message);
}
};
return {
employees,
loading,
error,
addEmployee,
loadEmployees
};
};
📊 Porovnání přístupů
| Přístup | Výhody | Nevýhody |
|---|---|---|
| Prop drilling | ✅ Jednoduché, explicitní | ❌ Neudržitelné, těsné spojení |
| Context API | ✅ Globální stav, čisté komponenty | ❌ Re-renders, složitější debug |
| Custom hooks | ✅ Logika oddělená, znovupoužitelné | ❌ Stále potřeba předávat hooks |
| State management | ✅ Výkonné, škálovatelné | ❌ Složitější setup |
📋 Praktické cvičení
- Identifikujte prop drilling v existující aplikaci
- Přepočítejte props v každé komponentě
- Navrhněte řešení pomocí Context API
- Implementujte custom hooks pro logiku
- Porovnejte výkon před a po refaktoringu
🚀 Tipy pro prevenci
1. Komponenta by měla používat většinu svých props
// ✅ Dobře - komponenta používá všechny props
const UserProfile = ({ user, onUpdate }) => {
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => onUpdate(user)}>Update</button>
</div>
);
};
// ❌ Špatně - komponenta nepoužívá většinu props
const UserProfile = ({ user, onUpdate, theme, filter, notifications }) => {
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => onUpdate(user)}>Update</button>
</div>
);
};
2. Použijte TypeScript pro lepší dokumentaci
interface UserProfileProps {
user: User;
onUpdate: (user: User) => void;
// Pokud máte více než 5 props, zvažte refaktoring
}
3. Rozdělte velké komponenty
// Místo jedné velké komponenty s mnoha props
const LargeComponent = ({ prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8 }) => {
// ...
};
// Rozdělte na menší komponenty
const ComponentA = ({ prop1, prop2, prop3 }) => { /* ... */ };
const ComponentB = ({ prop4, prop5, prop6 }) => { /* ... */ };
const ComponentC = ({ prop7, prop8 }) => { /* ... */ };
V příští části se podíváme na React DevTools pro debugging! 🔧