פיתוח רשימה עם סינון ב Solid JS
סוליד היא פריימוורק ריאקטיבית לפיתוח יישומי צד לקוח. היא דומה לריאקט בגלל השימוש ב JSX אבל יש לה מספר היבטים משמעותיים שונים, חלקם לטובה וחלקם פחות לטובה. בואו נראה חלק מהם באמצעות פיתוח קומפוננטה לרשימת פריטים עם סינון ב Solid JS.
1. מה אנחנו בונים
אפשר להיכנס לקוד הקומפוננטה המלא ולשחק איתו אונליין בקישור:
אחרי שתיכנסו תמצאו שם רשימה של ימי השבוע ותיבת סינון. כתיבת טקסט בתיבה תסנן את הרשימה ותציג רק את הימים שמתאימים לטקסט. שחקו עם זה קצת, ובואו נמשיך לדבר על הקוד.
2. סיגנלים במקום סטייט
רעיון מהפכני ראשון של סוליד הוא זיהוי התלויות האוטומטי - שזה בעצם הריאקטיביות. הרעיון הוא שמידע מסוים (שבריאקט היינו קוראים לו סטייט) יכול להשתנות, ומידע במקום אחר יכול להיות מושפע מהשינוי הזה. בריאקט כל ה Hooks API בנוי על העברת רשימות של תלויות כדי שריאקט יוכל לדעת מה משפיע על מה. בסוליד קריאה של ערך אוטומטית מוסיפה את ה"דבר" הזה לרשימת התלויות, ולכן חישוב הרשימה הוא אוטומטי.
בדוגמה שלנו יש לנו תיבת חיפוש והערך בתיבה משפיע על רשימת הימים שצריך להציג. הנה שתי השורות שגורמות לקסם הזה לקרות:
const [search, setSearch] = createSignal('');
const visibleItems = createMemo(
() => items().
map((i: string) => i.toLowerCase()).
filter((i: string) => i.includes(search()))
);
המשתנה search הוא סיגנל - כלומר הוא מחזיק מידע שכשמידע זה משתנה צריך להודיע לכל מי שתלוי במידע על השינוי. בשורה הבאה אני מפעיל את הפונקציה createMemo
שיוצרת משתנה שתלוי במידע. אני לא אומר לה באיזה מידע הוא תלוי, ובאופן אוטומטי סוליד מזהה מתי הפעלתי את הפונקציה search()
שמחזירה את המידע בסיגנל, ויוצר את הקשר.
הפעלת הפונקציה setSearch
תשלח את הסיגנל ותודיע ל visibleItems
שעליו להתעדכן.
מסיבה זו כל משתני הסטייט בסוליד הם בעצם פונקציות, והם יכולים להיווצר בכל מקום בתוכנית ולא רק בתוך קומפוננטות. כך הגדרתי את items בקוד הראשי של התוכנית:
const [items, _] = createSignal(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
סוליד עובד כברירת מחדל ב TypeScript, ולכן בשביל לקבל משתנה סטייט בתור Property של קומפוננטה אני צריך לתת לו את הטיפוס הנכון. הטיפוס נקרא Accessor והוא מוגדר בספריית סוליד עצמה. בגלל זה כתבתי שם:
interface IFilteredListProps {
items: Accessor<Array<string>>;
renderItem?: (item: string) => Node
}
בשביל לעדכן את search יש לנו פונקציית set, ממש כמו בריאקט. זאת תיבת החיפוש שמתאימה למשתנה:
<input type="text" value={search()} onInput={e => setSearch(e.currentTarget.value)} />
שווה לשים לב שבניגוד לריאקט, כאן האירוע נקרא input ולא change, כדי להתאים לטרמינולוגיה של ה DOM.
3. לולאות
טריק שני של סוליד שעובד אחרת מריאקט הוא הלולאות. שימו לב לקוד-
<ul>
<For each={visibleItems()}>
{(item, _index) => (
renderItem(item)
)}
</For>
</ul>
במקום map רגיל יש לנו קומפוננטה בשם For, שמקבלת מאפיין each שהוא ערך ריאקטיבי, ובנוסף בתור הילד היחיד היא מקבלת פונקציה שמחזירה את האלמנט שמתאים לכל פריט.
המימוש שלי ל renderItem
נראה ככה:
function defaultRenderItem(item: string) {
return (
<li>
<input type="checkbox" />
{item}
</li>
)
}
אז מה הסיפור עם הקומפוננטה, ולאן נעלם המאפיין key?
נתחיל בקומפוננטה - לסוליד יש קטע שרק קוד שכתוב בתוך קומפוננטות מושפע משינויים ריאקטיביים בסיגנלים. אצלנו יש שני סיגנלים items
ו search
, מהם מושפע סיגנל שלישי בשם visibleItems
וממנו מושפע הקטע שבתוך קומפוננטת ה For. כש search או items יתעדכן, אז visibleItems
יחושב מחדש ובתגובה קומפוננטת ה For
שמושפעת ממנו תחושב מחדש. זה ריאקטיביות כל הדרך.
הסיפור כאן מאוד שונה מריאקט, שם כל שינוי במשתנה state או prop כלשהו יגרום להרצה מחדש של כל פונקציית הקומפוננטה, ולא רק של החלק שמושפע מאותו השינוי. ברמת הביצועים בהנחה שהחיבורים בין הסיגנלים לדברים שמושפעים מהם לא גוזלים יותר מדי זיכרון, אז פעולת ה render של סוליד יותר מדויקת מזו של ריאקט ולכן יש פחות עבודה מיותרת של השוואות בין Virtual DOMs. למעשה בגישה הריאקטיבית של סוליד לא צריך Virtual DOM בכלל.
ומה לגבי המפתחות? באופן אוטומטי לולאות בסוליד מקבלות בתור מפתח את ה Reference של האוביקט עצמו, ולכן אוביקט שרק משנה מקום במערך מצליח לשמור על ה DOM Node שלו.
4. עכשיו אתם
בגלל שה Playground הוא אינטרקטיבי אתם יכולים לקחת את הקוד ולשחק איתו. נסו לעדכן את הקוד כך ש:
הוסיפו כפתור מחיקה שימחק את כל הימים המסומנים.
הוסיפו כפתור "ערבוב" שיערבב מחדש את הימים.
במקום להציג רק את הימים שמתאימים לחיפוש, צבעו את האותיות שמתאימות לחיפוש בכל אחד מהימים ברשימה שמכיל אותן.