הוק מותאם אישית (custom hook) הוא פונקציה ששמה מתחיל ב-use ושקוראת להוקים אחרים. כותבים אחד כששתי קומפוננטות צריכות את אותה לוגיקה עם state: מעבירים את קריאות ה-useState וה-useEffect לתוך הפונקציה, מחזירים את מה שהקומפוננטה צריכה, וקוראים לה מכל קומפוננטה.
שני הספוילרים קוראים ל-useToggle, ופתיחה של אחד משאירה את השני סגור. כל קריאה מריצה useState משלה, כך שכל קומפוננטה מקבלת ערך משלה. הוק מותאם משתף לוגיקה, לא state.
הכללים לכתיבת הוק
- השם מתחיל ב-
useובאות גדולה:useToggle,useOnlineStatus. כלל ה-lint של ההוקים משתמש בקידומת כדי למצוא קריאות להוקים ולבדוק אותן. - הוא קורא לפחות להוק אחד. פונקציית עזר שלא קוראת לשום הוק היא פונקציה רגילה; תנו לה שם כמו
formatDate, לאuseFormatDate, כדי שאפשר יהיה לקרוא לה בכל מקום, גם בתוך תנאים. - הוא עוקב אחרי כללי ההוקים: קוראים להוקים ברמה העליונה שלו, וקוראים לו רק מקומפוננטות או מהוקים מותאמים אחרים.
- הוא מחזיר כל מה שמועיל. מערך (
[on, toggle]) נוח כשמי שקורא לו משנה את שמות הערכים; אובייקט ({ data, loading, error }) נקרא טוב יותר כשיש שלושה ערכים או יותר.
useLocalStorage: state ששורד רענון
useState שוכח הכל ברענון. ההוק הזה קורא את הערך ההתחלתי מ-localStorage וכותב בחזרה כל שינוי.
התצוגה המקדימה רצה בתוך frame מבודד (sandbox) שבו הדפדפן חוסם את localStorage, אז כאן תראו את האזהרה ב-Console וההוק חוזר ל-state רגיל. בדף רגיל השם שורד רענון. ה-try/catch לא נועד רק לתצוגה המקדימה הזו: מצבי גלישה פרטית, עוגיות חסומות ומכסה מלאה גורמים לאחסון לזרוק שגיאה גם באפליקציות אמיתיות. העברת פונקציה ל-useState אומרת שהאחסון נקרא פעם אחת, ברינדור הראשון.
useDebounce: לחכות עד שהמשתמש מפסיק להקליד
תיבת חיפוש לא צריכה לשלוח בקשה בכל הקשה. useDebounce מחזיר עותק של ערך שמתעדכן רק אחרי שהוא הפסיק להשתנות לפרק זמן קבוע.
הקלידו מילה מהר: Typed עוקב אחרי כל מקש, Debounced משיג אותו חצי שנייה אחרי שאתם עוצרים, וה-Console רושם חיפוש אחד. כל הקשה משנה את value, ולכן React מריצה את ה-cleanup (clearTimeout) לפני שהיא מפעילה טיימר חדש. שנו את 500 ל-0 וכל הקשה רושמת חיפוש משלה.
useWindowWidth: הרשמה עם cleanup
הוק שמאזין לדפדפן חייב להסיר את ה-listener שלו כשהקומפוננטה נעלמת, אחרת כל mount משאיר אחד מאחור.
שנו את גודל חלון הדפדפן והמספר עוקב. לחצו על הכפתור וה-Console מראה שה-listener מוסר. מחקו את ה-return מהאפקט, ואז עשו mount ו-unmount כמה פעמים: מקבלים שורת added בכל פעם ואף שורת removed, כך שה-listeners הישנים מצטברים. לערכי דפדפן כמו זה, useSyncExternalStore הוא ההוק ש-React בנתה בדיוק לשם כך, וטוב לקרוא לו בתוך הוק מותאם.
הלוגיקה משותפת, ה-state לא
שתי קומפוננטות שקוראות לאותו הוק מקבלות שני עותקים נפרדים של כל מה שבתוכו. זה מפתיע אנשים שמצפים שהוק יעבוד כמו משתנה גלובלי.
לחצו על אחד הכפתורים ורק המונה הזה זז. אם שניהם צריכים להציג את אותו מספר, העבירו את ה-state ל-App והעבירו אותו מטה, או שימו אותו בקונטקסט. הוק מותאם יכול לעטוף את ה-state המשותף הזה לנוחות (useCart() שקורא CartContext), אבל השיתוף מגיע מהקונטקסט, לא מההוק.
מתי לכתוב הוק מותאם
חלצו הוק כשאותה קבוצה של קריאות להוקים מופיעה בשתי קומפוננטות, או כשלוגיקת האפקט של קומפוננטה אחת ארוכה מספיק כדי ששם יסביר אותה טוב יותר (useOnlineStatus() נקרא טוב יותר מעשרים שורות של listeners). אל תעטפו קריאה בודדת ל-useState בהוק רק כדי לשנות לה שם, ואל תכתבו הוקים שרק קוראים ל-useEffect כדי לקבוע state מ-props; חשבו את הערך הזה בזמן הרינדור במקום.
שאלות נפוצות
מה זה הוק מותאם אישית ב-React?
פונקציית JavaScript ששמה מתחיל ב-use ושקוראת להוק אחד או יותר. היא אורזת לוגיקה עם state (state, אפקטים, refs) כך שכל קומפוננטה יכולה להשתמש בה שוב על ידי קריאה לפונקציה.
האם הוקים מותאמים משתפים state בין קומפוננטות?
לא. כל קריאה להוק מותאם מריצה קריאות useState ו-useEffect משלה, כך שכל קומפוננטה מקבלת state עצמאי. כדי לשתף את אותו ערך בין קומפוננטות, הרימו את ה-state למעלה או שימו אותו בקונטקסט או ב-store חיצוני.
למה שם של הוק מותאם חייב להתחיל ב-use?
השם אומר לכללי ה-lint של React ולמפתחים אחרים שהפונקציה קוראת להוקים, ולכן כללי ההוקים חלים עליה: קוראים לה ברמה העליונה, אף פעם לא בתוך תנאי או לולאה. בלי הקידומת, ה-linter לא יכול לבדוק את הקריאות שבתוכה.
האם הוק מותאם יכול להחזיר JSX?
הוא יכול להחזיר כל דבר, אבל אם הוא מחזיר JSX בדרך כלל עדיף לכתוב אותו כקומפוננטה. הוקים מותאמים מחזירים בדרך כלל ערכים ופונקציות, כמערך ([value, toggle]) או כאובייקט ({ data, error, loading }).
אפשר לקרוא להוק מותאם בתוך useEffect או event handler?
לא. הוק מותאם הוא עדיין הוק, ולכן הוא עוקב אחרי כללי ההוקים: קוראים לו ברמה העליונה של קומפוננטה או של הוק מותאם אחר.