איך סוליד פתר את הבעיה הכי מעצבנת עם useEffect של ריאקט
הפונקציה useEffect של ריאקט מגדירה קשר חד כיווני בין מידע מסוים שאנחנו עוקבים אחריו לבין "משהו" חיצוני שצריך לקרות. בין הדוגמאות הפופולריות לאפקטים נוכל למצוא:
טיפים קצרים וחדשות למתכנתים
הפונקציה useEffect של ריאקט מגדירה קשר חד כיווני בין מידע מסוים שאנחנו עוקבים אחריו לבין "משהו" חיצוני שצריך לקרות. בין הדוגמאות הפופולריות לאפקטים נוכל למצוא:
משמעות הקיצור DRY היא Don't Repeat Yourself, ובהקשר של קוד אנחנו מתכוונים לא לחזור על אותו קטע קוד שוב ושוב. אבל מה אם קטע הקוד הזה הוא שורה אחת?
סוליד היא פריימוורק ריאקטיבית לפיתוח יישומי צד לקוח. היא דומה לריאקט בגלל השימוש ב JSX אבל יש לה מספר היבטים משמעותיים שונים, חלקם לטובה וחלקם פחות לטובה. בואו נראה חלק מהם באמצעות פיתוח קומפוננטה לרשימת פריטים עם סינון ב Solid JS.
רוב הזמן בעבודה על בעיית תכנות לא מושקע בהקלדה. בעיות התכנות המעניינות הן כאלה שאתה פשוט לא יודע מה להקליד כדי לפתור אותן, ולכן רוב הזמן מושקע במחקר: מחפשים רעיונות (בראש או ברשת), כותבים ומנסים אותם, רואים אם זה עובד ואז מתקנים, משפרים או עוברים לרעיון הבא. ורוב הזמן אנחנו לא יודעים כמה זמן ייקח עד שנגיע לפיתרון שעובד.
ויש משהו ממכר בממלכת האי וודאות הזאת, אפילו מותח. זה מרגיש שאנחנו עושים משהו חשוב - בונים עוד פיצ'ר, מתקנים עוד באג. עושים את העבודה.
תשוו את זה לכל הדברים שאנחנו לא עושים בפרויקט, אבל חשובים לא פחות:
כתיבת תיעוד.
עריכת היסטוריית הקומיטים בגיט כדי שתשקף נכון את העבודה שעשינו.
כתיבת בדיקות.
ריפקטורינג לפיתרון כדי שיהיה קל לתחזק אותו בהמשך.
טיפול במקרי קצה.
טיפול בשגיאות ויצירת Flows מתאימים למשתמשים במצבי שגיאה.
כל אחד מששת הסעיפים ברשימה הוא הצלחה וודאית - מרגע שמתחילים אותו הוא בטוח יצליח. ששת הסעיפים יחד יחזקו את הפיתרון הנוכחי שכתבנו, ויהפכו קוד שבמקרה עובד למנגנון בסיסי במערכת שאפשר להסתמך עליו ולבנות בעזרתו מנגנונים נוספים.
גם מבחינת השקעת זמן - החלק הכי קשה באי וודאות הוא האינטרקציה בין הפיתרון החדש שאני כותב לקוד הקיים במערכת. חיזוק הקוד הקיים באמצעות תיעוד, בדיקות וטיפול בכל מקרי הקצה, מצמצם משמעותית את האי וודאות ומאפשר לי להתקדם יותר מהר בפיתרון בעיות חדשות.
אבל עזבו אתכם הצלחות וודאיות וקוד נכון... למי יש זמן לזה... יאללה מה הבאג הבא?
טכניקות עיצוב ריספונסיבי רבות כוללות שינוי גדלים של אלמנטים כשהעמוד משנה גודל, ובאמת עם CSS אפשר להגיע לתוצאות טובות כשמה שמעניין אותנו הוא המסך כולו. אבל ברגע שאנחנו מתחילים לשלב JavaScript העסק הופך יותר מסובך - קוד JavaScript יכול לגרום לשינוי גודל של אלמנט מסוים בעמוד, בלי לשנות את הגודל של כל החלון.
ממשק חדש שזמין כבר בכל הדפדפנים המרכזיים בשם Resize Observer יכול לעזור לנו לזהות שינויים כאלה ולהגיב אליהם, וכך אנחנו לא צריכים כל פעם שכותבים JavaScript בעמוד לחשוב על איזה גדלים הוא עלול להשפיע. בואו נראה דוגמה לממשק זה ודרכה גם אסביר איך הוא עובד.
אם יש לכם קוד שצריך להתנהג בצורה מסוימת במערכת ה"רגילה" ובצורה אחרת בבדיקות, הרבה פעמים לא נרצה ללכלך את הקוד הרגיל ב if-ים שיבדקו באיזה מצב אנחנו. תבנית Factory יכולה לתת פיתרון פשוט לבעיה.
בין כל הפעמים שאנחנו מריצים npx npm-check-updates
או bundle update
או אפילו pip install -U
, יש דבר אחד שנוטים לשכוח - גם מבנה הפרויקט הוא תלות, וגם אותו אפשר לשדרג:
אולי יש לכם מערכת Build מאוד מתוחכמת שבניתם לבד ב gulp בימים שהוא עוד היה אופנתי, והיא עדיין עובדת אבל כבר אי אפשר לשנות בה כלום.
אולי יש לכם סט בדיקות ממש מוצלח שעדיין כתוב בג'סמין ומורץ עם karma.
אולי יש לכם סקריפט שפשוט בונה את הפרויקט על מכונת הפיתוח ומעתיק לשרת את הקבצים עם rsync.
ולמה בעצם חשוב לשדרג את מבנה הפרויקט? הרי מה שעובד לא נוגעים וכו' לא? אז ככה:
היום יש דרכים יותר טובות לעשות את מה שלפני שנתיים היה קשה. בבניה של פרויקט ווב אולי כבר לא צריך לטרנספל לתמיכה ב IE6. אולי כבר אפשר להשתמש ב HTTP/2 Server Push כדי לשפר ביצועים.
לאינטרנט יש נטיה להפוך מידע עדכני לזמין יותר, לפחות במה שנוגע לשאלות טכניות. לכן יהיה לי יותר קל למצוא טיפים על ג'סט מאשר על ג'סמין, ועל webpack במקום על gulp.
מתכנתים מכירים את הכלים החדשים מפרויקטים אחרים, ולאט לאט יהיה יותר קשה למצוא מתכנתים שעדיין זוכרים את הכלים הישנים. ככל שהתהליך הזה קורה ככה יותר קשה לכם להוסיף או לעדכן קוד בחלק של הגדרות הפרויקט.
שידרוג מבנה הפרויקט זו משימה אפילו יותר קשה משידרוג התלויות הרגיל, כי בדרך כלל זה יכריח אותנו לשכתב אזורים בקוד שכמעט לא נוגעים בהם. אבל אם הפרויקט שלכם חי כבר כמה שנים, שידרוג כזה יהיה מאוד מורגש בעבודה היום יומית.
עד גירסה 6 של ריילס לא היתה דרך להריץ בדיקות בצורה מקבילית. לא היתה דרך זו מילה גדולה, גיטהאב כתבו אינסוף קוד כדי לעקוף את הפריימוורק ולהריץ את הבדיקות בצורה מקבילית, אבל זה היה גיטהאב.
בסוף נמאס להם לעקוף מגבלות של הפריימוורק ובגירסה 6 של ריילס הם כבר תרמו את כל המנגנון המתוחכם שלהם.
עכשיו בואו נחזור אליכם ונניח שגם אתם, כמו גיטהאב, בניתם מנגנון סופר מתוחכם להרצת בדיקות בריילס בצורה מקבילית. מה עושים כשריילס יוצאים עם גירסה 6 ומציעים הרצת בדיקות במקביל built in? האם אתם זורקים את כל מה שכתבתם ועוברים לפיתרון הסטנדרטי?
או ננסה תרגיל יותר פשוט - נניח שישבתם שבועיים כדי לבנות בלוג שקורא קבצי markdown מאפס, ואז אחרי שבועיים של עבודה כשיש לכם גירסה ראשונה עובדת אתם מגלים את hugo. האם תזרקו את כל מה שעשיתם ותתחילו מחדש עם הוגו? או שכבר יש לכם משהו שעובד וחבל לוותר על זה?
דרך טובה להסתכל על החלטות כאלה היא לשים לב שהעלות שכבר השקעתם היא עלות שקועה. שקועה, כלומר שהיא במים, היא לא תחזור יותר. לעולם לא תקבלו בחזרה את השבועיים שהשקעתם בהקמת פלטפורמה לבלוג או את החודשיים שהשקעתם בהקמת סביבה להרצת בדיקות במקביל בריילס.
ועכשיו יש לנו החלטה חדשה לקבל-
בהינתן כל מה שאני יודע היום, נניח שמישהו היה מציע לי בחירה בין שני המנגנונים (הבלוג שלי והוגו), איזה מנגנון הייתי בוחר?
האם הייתי מעדיף פיתרון שהושקעו בו שבועיים ועדיין לא נוסה בפרודקשן, או פיתרון שהושקעו בו שנים ויש לו המון משתמשים פעילים?
אם כשמישהו אחר היה מציע לי את הפיתרון "שלי" לא הייתי לוקח, אז עדיף לא לקחת גם אם אני הוא זה שמציע. הזמן שהשקעתי לא יהפוך את הפיתרון שלי לטוב יותר.
שני מאפייני ה Layout המתקדמים של CSS, פלקסבוקס וגריד, כוללים מגבלה קצת מעצבנת: הם מכילים את הגדרות העיצוב על הילדים הבלוקים הישירים שלהם.
במילים אחרות אם יש לי פס עליון באתר ובו 6 לינקים, וה HTML שלי נראה ככה:
<div class="topbar">
<a href="#">link #1</a>
<a href="#">link #2</a>
<a href="#">link #3</a>
<a href="#">link #4</a>
<a href="#">link #5</a>
<a href="#">link #6</a>
</div>
אז אין בעיה להשתמש ב CSS כזה כדי לסדר את כל הלינקים בתוך פלקסבוקס ולחלק להם את המקום שווה בשווה:
.topbar {
display: flex;
}
.topbar a {
padding: 0.2rem;
flex: 1;
}
אבל אם במקרה החלטתי לחלק את הלינקים ב HTML למספר "בלוקים", רק מבחינה סמנטית או כי היה לי יותר נוח לייצר HTML כזה:
<div class="topbar">
<div class="part1">
<a href="#">link #1</a>
<a href="#">link #2</a>
<a href="#">link #3</a>
</div>
<div class="part2">
<a href="#">link #4</a>
<a href="#">link #5</a>
</div>
<div class="part3">
<a href="#">link #6</a>
</div>
</div>
אז פתאום כל הפלקסבוקס שלי נשבר. יותר מדויק להגיד שהוא לא נשבר פשוט האלמנטים שמסודרים בפלקסבוקס הם עכשיו הדיבים part1, part2 ו part3, ולא הלינקים המקוריים.
הערך contents למאפיין display, שנתמך ברוב הדפדפנים (חוץ מ IE כמובן), מספק דרך קלה "לדלג" על אלמנטים כשמסדרים אותם בתוך פלקסבוקס או גריד. ה CSS הבא יגרום גם ל HTML השני להציג את הלינקים בצורה יפה בתוך פלקסבוקס:
.topbar {
display: flex;
}
.topbar > div {
display: contents;
}
.topbar a {
padding: 0.2rem;
flex: 1;
}
וככה זה נראה לייב בקודפן:
אם יש משהו שקל למתכנתי Node להסכים עליו זה ש npm שבור. הבעיות המרכזיות בו הן:
מלחמת שמות נוראית במאגר המרכזי, שמובילה לרישום חבילות פיקטיביות וחבילות מזיקות (כן כמו 17 חבילות שונות לגניבת טוקנים של דיסקורד)
הגדרה "גמישה" של גירסאות ב package.json וקובץ package-lock.json שחצי מהאנשים מוחקים אותו כל פעם שיש קונפליקט.
תלות במאגר מרכזי לכל בניה (היוש left-pad)
התקנת חבילות שכוללות אינסוף זבל בנוסף לקבצים שרצית, ורק מנפחות את node_modules
(היוש readline)
דינו, הניסיון השני של ריאן דל ליצור סביבת ריצה ל JavaScript מחוץ לדפדפן, מתמודד עם ניהול חבילות חיצוניות בצורה מובנית, מבוזרת ודי מעניינת.
דינו משתמש ב import ולא ב require, אבל השוס הגדול הוא שבמקום להעביר ל import שם של חבילה אנחנו מעבירים את ה URL המלא אליה. כלומר בשביל להשתמש ב lodash בתוכנית דינו אני אכתוב:
import * as _ from 'https://cdn.skypack.dev/-/lodash-es@v4.17.21-rDGl8YjBUjcrrAbjNrmo/dist=es2019,mode=imports/optimized/lodash-es.js';
const squares = _.map(_.range(10), (x) => x * x);
console.log(squares);
וזה כבר פותר חלק גדול מהבעיות של npm:
בגלל שאני מעביר URL, לא צריך ללכת מכות על שמות.
הגדרת הגירסה היא חלק מה URL והיא מאוד ספציפית, כמו בתגיות ה script שהיו לנו פעם בקבצי HTML, לפני שעברנו לוובפאק. וגם אין ולא צריך package.json. גם לא צריך ללמוד את ההבדל בין devDependencies ל peerDependencies ול dependencies רגילים. מה שעושים לו import נטען.
לא צריך מאגר מרכזי.
מורידים רק מה שמשתמשים בו ולא חבילה שלמה
בעיה שכבר אפשר לראות במנגנון הזה היא מה קורה כשהתוכן שב URL משתנה, והאם זה לא ישבור לי את כל הפרויקט. אז האמת שגם על זה הם חשבו ואפשר ליצור קובץ נעילה ששומר hash של המודול, ואז בהורדה הבאה לוודא שה hash לא השתנה. אם נניח קראתי לתוכנית שלי demo.js
אז על המכונה שלי במצב פיתוח אני אכתוב:
$ deno cache --lock=lock.json --lock-write -r demo.js
וזה ייצור לי קובץ בשם lock.json על המחשב עם התוכן:
{
"https://cdn.skypack.dev/-/lodash-es@v4.17.21-rDGl8YjBUjcrrAbjNrmo/dist=es2019,mode=imports/optimized/lodash-es.js": "4a1b35d34b5e0d3ae68aa46ddaabb9700ccecb75b830974db7c46a271f10daa8"
}
עכשיו כשאני אעביר את התוכנית למכונה אחרת אני מעביר איתה את קובץ ה lock.json שיצרתי, ומפעיל שם:
$ deno cache --lock=lock.json -r 03-with-lodash.js
ודינו יוריד את כל החבילות החיצוניות וישווה את ה hash-ים שלהן מול מה שכתוב בקובץ המנעול. אחרי זה בדרך כלל נריץ עם:
$ deno run --lock=lock.json --cached-only 03-with-lodash.js
וזה מוודא שכל החבילות נטענות רק מה cache ושום דבר חדש לא יורד מהרשת.
האם זה יתפוס? זאת השאלה הגדולה. במעבר לדינו נצטרך להתרגל לחפש שוב URL-ים מלאים ב cdn-ים במקום פשוט להתקין עם npm install
ושם קצר של חבילה. לאורך זמן הגישה של דינו נשמעת לי יותר יציבה, אבל יהיה קשה להיפרד מ npm install lodash
.