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 שיתוף נתונים גלובלי

הקודם: ← אירועים
הבא: רינדור מותנה →

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. מה חוקי ה-Hooks?

    1. אין חוקים
    2. רק במחלקות
    3. רק בתוך לולאה
    4. לקרוא להם רק ברמה העליונה של קומפוננטה או Hook, לא בתוך תנאי או לולאה
    הצגת התשובה

    תשובה ד. React מזהה כל Hook לפי סדר הקריאה, ולכן הסדר חייב להיות קבוע.

  2. מתי רץ useEffect(fn, [])?

    1. פעם אחת אחרי הרינדור הראשון
    2. בכל רינדור
    3. לפני הרינדור
    4. אף פעם
    הצגת התשובה

    תשובה א. מערך תלויות ריק = אין תלויות שמשתנות, ולכן רק בהרכבה.

  3. למה משמש useRef?

    1. לשמור ערך שלא גורם לרינדור, או לגשת לאלמנט DOM
    2. לעצב אלמנט
    3. לשלוח בקשות
    4. להחליף את useState
    הצגת התשובה

    תשובה א. שינוי ref.current לא מרנדר מחדש.