Props Drilling והרמת State
מבוא
ככל שאפליקציה גדלה ועץ הקומפוננטות מסתעף, צצות שתי בעיות נפוצות בניהול הנתונים:
- Props Drilling - הצורך להעביר props דרך רמות ביניים רבות שאינן זקוקות להם
- שיתוף State - מצב שבו כמה קומפוננטות צריכות לקרוא ולעדכן את אותו נתון
בשיעור זה נכיר את שתי הבעיות ואת הפתרון הבסיסי לשיתוף State - Lifting State Up.
Props Drilling - הבעיה
Props Drilling קורה כשמעבירים props דרך קומפוננטות ביניים שלא צריכות אותם:
// ❌ Props Drilling - הנתון עובר דרך 3 רמות!
function App() {
const [user, setUser] = useState({ name: 'דני' });
return <Layout user={user} />; // רמה 1
}
function Layout({ user }) {
return (
<div>
<Sidebar user={user} /> {/* רמה 2 - לא צריך את user */}
<Content />
</div>
);
}
function Sidebar({ user }) {
return (
<div>
<UserProfile user={user} /> {/* רמה 3 - לא צריך את user */}
</div>
);
}
function UserProfile({ user }) {
return <p>שלום, {user.name}!</p>; // רמה 4 - כאן באמת צריך!
}
בעיות של Props Drilling:
- קוד מסורבל וקשה לתחזוקה
- קומפוננטות ביניים "מזוהמות" ב-props לא רלוונטיים
- קשה לשנות את מבנה הנתונים
Lifting State Up - פתרון לשיתוף State
העיקרון של Lifting State Up ("הרמת המצב") פשוט: כאשר כמה קומפוננטות צריכות לשתף נתון, מעבירים אותו למעלה - אל קומפוננטת ההורה המשותפת הקרובה ביותר. ההורה מחזיק את ה-State ומעביר אותו לילדים כ-props, יחד עם פונקציית עדכון. כך כל הילדים עובדים על אותו מקור נתונים יחיד ונשארים מסונכרנים.
הבעיה: שתי קומפוננטות צריכות את אותו נתון
// ❌ בעיה - כל קומפוננטה עם state נפרד
function TemperatureInput() {
const [celsius, setCelsius] = useState(0);
return <input value={celsius} onChange={e => setCelsius(e.target.value)} />;
}
function TemperatureDisplay() {
const [celsius, setCelsius] = useState(0); // לא מסונכרן!
return <p>{celsius}°C = {(celsius * 9/5) + 32}°F</p>;
}
הפתרון: Lifting State Up
// ✅ פתרון - State בהורה המשותף
function TemperatureConverter() {
// State מורם להורה
const [celsius, setCelsius] = useState(0);
return (
<div>
<TemperatureInput
value={celsius}
onChange={setCelsius}
/>
<TemperatureDisplay celsius={celsius} />
</div>
);
}
function TemperatureInput({ value, onChange }) {
return (
<input
type="number"
value={value}
onChange={e => onChange(Number(e.target.value))}
/>
);
}
function TemperatureDisplay({ celsius }) {
const fahrenheit = (celsius * 9/5) + 32;
return <p>{celsius}°C = {fahrenheit}°F</p>;
}
דוגמה מלאה: טופס עם ולידציה
function RegistrationForm() {
// State מורם - כל הנתונים בהורה
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
// פונקציית עדכון שמועברת לילדים
const updateField = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
// ולידציה
const validate = () => {
const newErrors = {};
if (!formData.email.includes('@')) {
newErrors.email = 'אימייל לא תקין';
}
if (formData.password.length < 6) {
newErrors.password = 'סיסמה חייבת להכיל לפחות 6 תווים';
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'הסיסמאות לא תואמות';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return (
<form onSubmit={e => { e.preventDefault(); validate(); }}>
<EmailInput
value={formData.email}
onChange={v => updateField('email', v)}
error={errors.email}
/>
<PasswordInput
value={formData.password}
onChange={v => updateField('password', v)}
error={errors.password}
/>
<ConfirmPasswordInput
value={formData.confirmPassword}
onChange={v => updateField('confirmPassword', v)}
error={errors.confirmPassword}
/>
<FormSummary formData={formData} errors={errors} />
<button type="submit">הרשם</button>
</form>
);
}
function EmailInput({ value, onChange, error }) {
return (
<div>
<input
type="email"
value={value}
onChange={e => onChange(e.target.value)}
placeholder="אימייל"
/>
{error && <span className="error">{error}</span>}
</div>
);
}
function PasswordInput({ value, onChange, error }) {
return (
<div>
<input
type="password"
value={value}
onChange={e => onChange(e.target.value)}
placeholder="סיסמה"
/>
{error && <span className="error">{error}</span>}
</div>
);
}
function ConfirmPasswordInput({ value, onChange, error }) {
return (
<div>
<input
type="password"
value={value}
onChange={e => onChange(e.target.value)}
placeholder="אימות סיסמה"
/>
{error && <span className="error">{error}</span>}
</div>
);
}
function FormSummary({ formData, errors }) {
const isValid = Object.keys(errors).length === 0 &&
formData.email && formData.password;
return (
<div className="summary">
<p>סטטוס: {isValid ? '✅ תקין' : '❌ לא מלא'}</p>
</div>
);
}
דפוס: Controlled Component
קומפוננטה מבוקרת (Controlled Component) היא קומפוננטה שאינה מחזיקה State משלה, אלא מקבלת את הערך שלה מההורה ומדווחת לו על כל שינוי. זהו יישום ישיר של Lifting State Up, והדפוס המקובל לעבודה עם שדות טופס ב-React:
// ✅ Controlled Component
function TextInput({ value, onChange, label }) {
return (
<label>
{label}:
<input
value={value} // ערך מההורה
onChange={e => onChange(e.target.value)} // עדכון להורה
/>
</label>
);
}
// שימוש
function Parent() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
return (
<div>
<TextInput label="שם" value={name} onChange={setName} />
<TextInput label="אימייל" value={email} onChange={setEmail} />
</div>
);
}
מתי להשתמש בכל גישה?
| מצב | פתרון מומלץ |
|---|---|
| 2-3 קומפוננטות משתפות state | Lifting State Up |
| Props עוברים דרך 3+ רמות | Context API (שיעור 15) |
| State גלובלי מורכב | Redux / Zustand |
| State מקומי בלבד | useState רגיל |
טעויות נפוצות
1. הרמה לא נכונה
// ❌ שגוי - State ברמה גבוהה מדי
function App() {
const [inputValue, setInputValue] = useState(''); // לא צריך להיות כאן!
return <DeepNestedComponent value={inputValue} />;
}
// ✅ נכון - State בהורה המשותף הקרוב ביותר
function FormSection() {
const [inputValue, setInputValue] = useState('');
return <SomeInput value={inputValue} onChange={setInputValue} />;
}
2. שכפול State
// ❌ שגוי - State משוכפל
function Child({ user }) {
const [localUser, setLocalUser] = useState(user); // שכפול!
// localUser לא יתעדכן כשהprop משתנה
}
// ✅ נכון - שימוש ישיר ב-prop
function Child({ user, onUserChange }) {
// ללא state מקומי, עובדים ישירות עם ה-prop
}
סיכום
| מושג | הסבר |
|---|---|
| Props Drilling | העברת props דרך קומפוננטות ביניים |
| Lifting State Up | העלאת state להורה משותף |
| Controlled Component | קומפוננטה ש-state שלה מנוהל בחוץ |
| Single Source of Truth | מקור אחד לנתון |
הקודם: ← Lifecycle & useEffect
הבא: Component Communication Patterns →
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
מה זה Props Drilling?
הצגת התשובה
תשובה ג. קומפוננטות באמצע מעבירות נתונים רק כדי שיגיעו למטה.
-
מה זה Lifting State Up?
הצגת התשובה
תשובה ב. כך מקור אמת אחד משותף לכל מי שצריך אותו.