Hooks
מהם Hooks?
Hooks הם פונקציות מיוחדות שמאפשרות "להתחבר" ליכולות הליבה של React - ניהול State, תופעות לוואי, גישה ל-DOM ועוד - ישירות מתוך Function Components. עד שנוספו בגרסה 16.8 (שנת 2019), יכולות אלו היו זמינות רק בקומפוננטות מחלקה. ה-Hooks הם שהפכו את הקומפוננטות הפונקציונליות לדרך העבודה המקובלת ב-React.
import { useState, useEffect } from 'react';
function Counter() {
// useState הוא Hook!
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
חוקי Hooks
React מזהה את ה-Hooks לפי הסדר שבו הם נקראים בכל רינדור. כדי שהסדר יישאר קבוע, יש לשמור על שני כללים ברזל:
1. רק ברמה העליונה
אין לקרוא ל-Hook בתוך תנאי, לולאה או פונקציה מקוננת - רק ישירות בגוף הקומפוננטה. כך React מבטיחה שאותם Hooks נקראים באותו סדר בכל רינדור:
// ✅ נכון - ברמה העליונה
function Component() {
const [count, setCount] = useState(0);
if (count > 5) {
// ...לוגיקה
}
}
// ❌ שגוי - בתוך תנאי
function Component() {
if (someCondition) {
const [count, setCount] = useState(0); // אסור!
}
}
// ❌ שגוי - בתוך לולאה
function Component() {
for (let i = 0; i < 3; i++) {
const [x, setX] = useState(0); // אסור!
}
}
2. רק בקומפוננטות React (או Custom Hooks)
// ✅ נכון - בקומפוננטה
function MyComponent() {
const [state, setState] = useState(0);
}
// ✅ נכון - ב-Custom Hook
function useMyHook() {
const [state, setState] = useState(0);
return state;
}
// ❌ שגוי - בפונקציה רגילה
function regularFunction() {
const [state, setState] = useState(0); // אסור!
}
useState
כבר הכרנו את ה-Hook הזה בשיעור על State. הנה תזכורת קצרה:
const [value, setValue] = useState(initialValue);
// דוגמאות
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const [items, setItems] = useState([]);
const [user, setUser] = useState(null);
useEffect
Hook זה מאפשר לסנכרן את הקומפוננטה עם העולם שמחוץ ל-React - פעולות שנקראות side effects (תופעות לוואי). בין השימושים הנפוצים:
- קריאות לשרת (API)
- הרשמה לאירועים והאזנה להם (Subscriptions)
- שינוי ידני של ה-DOM
- טיימרים ומרווחי זמן
מערך התלויות ([dependencies]) הוא הלב של ה-Hook: הוא קובע מתי האפקט ירוץ מחדש. פונקציית ה-cleanup שמחזירים מנקה אחריו - למשל מבטלת טיימר או מסירה מאזין.
התחביר
useEffect(() => {
// הקוד שירוץ
return () => {
// cleanup (אופציונלי)
};
}, [dependencies]); // מערך תלויות
ריצה בכל רינדור
useEffect(() => {
console.log('רץ בכל רינדור');
});
// ללא מערך תלויות!
ריצה פעם אחת (mount)
useEffect(() => {
console.log('רץ רק פעם אחת בטעינה');
fetchData();
}, []); // מערך ריק!
ריצה כשתלויות משתנות
useEffect(() => {
console.log('count השתנה:', count);
}, [count]); // רץ כש-count משתנה
Cleanup
useEffect(() => {
const interval = setInterval(() => {
console.log('טיק');
}, 1000);
// Cleanup - רץ לפני unmount או לפני הריצה הבאה
return () => {
clearInterval(interval);
};
}, []);
דוגמה: Fetch Data
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [userId]); // רץ מחדש כשה-userId משתנה
if (loading) return <p>טוען...</p>;
if (error) return <p>שגיאה: {error}</p>;
return <h1>{user.name}</h1>;
}
useRef
ל-useRef שני שימושים עיקריים: לגשת ישירות לאלמנט DOM (למשל כדי למקד שדה קלט), ולשמור ערך שנשמר בין רינדורים אך אינו גורם לרינדור מחדש כשהוא משתנה. זהו ההבדל המרכזי מ-State.
Reference ל-DOM
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</div>
);
}
שמירת ערך (לא גורם לרינדור)
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
return (
<div>
<p>{count}</p>
<button onClick={start}>התחל</button>
<button onClick={stop}>עצור</button>
</div>
);
}
useMemo
useMemo מיישם Memoization: הוא שומר את התוצאה של חישוב "כבד" ומריץ אותו מחדש רק כשאחת התלויות משתנה. כך נמנעים מחישובים מיותרים בכל רינדור.
function ExpensiveComponent({ items, filter }) {
// מחושב מחדש רק כש-items או filter משתנים
const filteredItems = useMemo(() => {
console.log('מחשב...');
return items.filter(item => item.name.includes(filter));
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
useCallback
בדומה ל-useMemo, אך עבור פונקציות: useCallback שומר את אותה פונקציה בין רינדורים במקום ליצור אותה מחדש בכל פעם. הוא שימושי במיוחד כשמעבירים callback לקומפוננטת ילד עטופה ב-React.memo, כדי למנוע רינדור מיותר שלה.
function Parent() {
const [count, setCount] = useState(0);
// הפונקציה לא נוצרת מחדש בכל רינדור
const handleClick = useCallback(() => {
console.log('נלחץ');
}, []); // תלויות ריקות = לא משתנה לעולם
return <Child onClick={handleClick} />;
}
const Child = React.memo(({ onClick }) => {
console.log('Child rendered');
return <button onClick={onClick}>לחץ</button>;
});
useContext
מאפשר לשתף נתונים בין קומפוננטות מרוחקות בעץ מבלי להעביר props דרך כל רמת ביניים (Props Drilling). זו הצצה ראשונה ל-Context API, שנרחיב עליו בשיעור נפרד.
import { createContext, useContext, useState } from 'react';
// יצירת Context
const ThemeContext = createContext();
// Provider - עוטף את האפליקציה
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Main />
</ThemeContext.Provider>
);
}
// צריכת ה-Context בכל קומפוננטה
function Header() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<header className={theme}>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
החלף ערכת נושא
</button>
</header>
);
}
Custom Hooks
מעבר ל-Hooks המובנים, אפשר לכתוב Hooks משלנו. Custom Hook הוא פונקציה ששמה מתחיל ב-use ומרכזת בתוכה לוגיקה מבוססת-Hooks, כך שאפשר לעשות בה שימוש חוזר בכמה קומפוננטות בלי לשכפל קוד:
useLocalStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// שימוש
function App() {
const [name, setName] = useLocalStorage('name', '');
// ...
}
useWindowSize
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// שימוש
function Component() {
const { width, height } = useWindowSize();
return <p>גודל חלון: {width}x{height}</p>;
}
סיכום Hooks
| Hook | שימוש |
|---|---|
useState |
ניהול state |
useEffect |
side effects (API, timers) |
useRef |
reference ל-DOM או ערך קבוע |
useMemo |
memoization של ערך מחושב |
useCallback |
memoization של פונקציה |
useContext |
שיתוף נתונים גלובלי |
הקודם: ← אירועים
הבא: רינדור מותנה →
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
מה חוקי ה-Hooks?
הצגת התשובה
תשובה ד. React מזהה כל Hook לפי סדר הקריאה, ולכן הסדר חייב להיות קבוע.
-
מתי רץ
useEffect(fn, [])?הצגת התשובה
תשובה א. מערך תלויות ריק = אין תלויות שמשתנות, ולכן רק בהרכבה.
-
למה משמש
useRef?הצגת התשובה
תשובה א. שינוי ref.current לא מרנדר מחדש.