מדריך Vue למתחילים - פרק 2 - תקשורת בין קומפוננטות באותו עץ

בפרק הראשון של המדריך למדנו את העקרונות המרכזיים של Vue ואיך לכתוב קומפוננטות פשוטות. אפליקציית Vue מלאה מורכבת ממספר קומפוננטות ולכן בפרק זה נדבר על הקשר בין מספר קומפוננטות ב Vue ועל העברת המידע ביניהן.

1. העברת props

הרעיון הראשון שצריך לקחת כשאנחנו בונים אפליקציית Vue הוא שאנחנו רוצים לארגן את כל האפליקציה שלנו בתור אוסף של קומפוננטות, שנקרא גם עץ של קומפוננטות (Component Tree). כל דף אינטרנט אפשר לחלק לקומפוננטות והרבה פעמים אנחנו נמצא קומפוננטות מסוימות מופיעות יותר מפעם אחת בעמוד. בעמוד הבית של גוגל לדוגמה אפשר לראות בשורה העליונה את כפתורי הניווט Gmail, Images, תפריט ו Sign In; לאחר מכן באמצע העמוד יש תמונה ומתחתיה תיבת חיפוש; בהמשך העמוד שני הכפתורים Google Search ו I'm Feeling Lucky; המשך לבחירת שפה ולמטה אזור ה Footer. בשביל לבנות מהעמוד שלהם עץ קומפוננטות כל מה שצריך הוא להסתכל ולהגדיר:

  1. קומפוננטה של שורת ניווט

  2. בתוכה תופיע פעמיים קומפוננטה של כפתור ניווט, פעם אחת עם הטקסט Gmail ופעם שניה עם הטקסט Images

  3. לידן קומפוננטה נוספת של תפריט נפתח

  4. ואז שוב קומפוננטה של כפתור ניווט הפעם כפתור Primary עם הטקסט Sign In

  5. קומפוננטה של תמונה מרכזית

  6. קומפוננטה לתיבת החיפוש (עם הרמקול הקטן בצד שמאל והשלמה אוטומטית איך שמתחילים לחפש)

  7. קומפוננטה לשני הכפתורים

  8. קומפוננטה לבחירת השפות

  9. קומפוננטה לאזור ה Footer

האם זאת החלוקה "הנכונה"? ברור שלא, יש המון חלוקות אפשריות ואין חלוקה אחת נכונה. החלוקה לקומפוננטות עוזרת לנו לחשוב על הבעיה והרבה פעמים במהלך כתיבת האפליקציה אנחנו נשנה את בחירת הקומפוננטות עד שנגיע למשהו שבדיוק מסתדר.

עכשיו בואו נעשה זום-אין על מספר 2 ברשימה שלי, אלה כפתורי הניווט. כפתורי ניווט מאוד דומים אחד לשני מבחינה זאת שיש להם את אותו HTML, אותו עיצוב CSS (לרוב, כי כפתור Sign In כולל צבעים אחרים) ואותו קוד JavaScript. כשיש לנו קומפוננטה שצריכה להופיע מספר פעמים בעמוד בשינויים קטנים אנחנו נשתמש במנגנון Props כדי להתאים את המאפיינים הספציפיים של כל מופע.

מנגנון props אומר שביצירת הקומפוננטה אנחנו יכולים להעביר "פרמטרים" לאותה קומפוננטה, והקומפוננטה תוכל להשתמש בפרמטרים אלה כדי להציג את המידע. במקרה של כפתור ניווט לדוגמה נוכל להעביר לכל "מופע" של הקומפוננטה טקסט אחר וקישור אחר, וכך להשתמש באותה קומפוננטה מספר פעמים ולקבל כל פעם תוצאה שונה - משהו כמו התבנית הבאה:

<template>
  <NavLink text="Images" href="https://images.google.com" />
  <NavLink text="Gmail" href="https://google.com/mail" />
  <NavLink
    text="Sign In"
    href="https://accounts.google.com/ServiceLogin"
    :primary="true"
  />
</template>

בצד של הקומפוננטה אני צריך להגדיר איזה props אני מצפה לקבל (אפשר להגדיר גם מאיזה סוג כל prop צריך להיות), ואז אני יכול להשתמש ב props אלה בתוך התבנית. הנה קוד הקומפוננטה:

<template>
  <a href="#" :class="{ 'primary-link': primary }">{{ text }}</a>
</template>

<script>
export default {
  props: ["text", "href", "primary"],
  name: "NavLink",
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.primary-link {
  background: #1a73e8;
  color: white;
  border-radius: 5px;
}

a {
  margin: 5px;
  text-decoration: none;
  padding: 10px;
}

a:hover {
  text-decoration: underline;
}
</style>

2. מה זה כל הנקודותיים האלה?

שמתם לב לסימן הנקודותיים לפני המילה class בקוד הקומפוננטה, ולפני המילה primary בקוד התבנית? בטח שאלתם את עצמכם מה המשמעות שלהם ולמה הם מופיעים לפני מאפיינים מסוימים אבל לא מופיעים לפני מאפיינים אחרים. בשביל התשובה שימו לב לערכים שאני מעביר במאפיינים שמקבלים נקודותיים, לעומת הערכים שעוברים דרך מאפיינים ללא נקודותיים:

  1. המאפיין text מקבל מידע מסוג String ועובר ללא נקודותיים.

  2. המאפיין href מקבל מידע מסוג String ועובר ללא נקודותיים.

  3. המאפיין primary מקבל מידע מסוג Boolean ועובר עם נקודותיים.

  4. המאפיין class מקבל מידע מסוג אוביקט ועובר עם נקודותיים.

ההתאמה בין סוג הערך לנקודותיים היא לא מקרית. ב Vue, כשרוצים להעביר ערך שאיננו מחרוזת יש להשתמש בנקודותיים כדי לגרום ל Vue לשערך את הביטוי בתור קוד JavaScript ולהעביר את התוצאה שלו.

בקישור הבא תמצאו את דוגמת הקוד בתוך קודסנדבוקס כדי שתוכלו לשחק בעצמכם עם העברת props לקומפוננטה: https://codesandbox.io/s/winter-tdd-xglp3.

3. שליחת אירועים

מנגנון תקשורת שני שחשוב להכיר הוא התקשורת בכיוון ההפוך - מקומפוננטה פנימית אל הקומפוננטה שמכילה אותה. אם Property הם הדרך שלנו להעביר מידע מבחוץ פנימה לקומפוננטה, אז אירועים או Events, הם הדרך שלנו להוציא מידע מקומפוננטה כלפי מעלה בעץ. נשים בצד את הדוגמה של כפתורי הניווט ונעבור לדוגמה עם כפתורים אמיתיים: מאיזושהי סיבה החלטתם לכתוב אפליקציה שמראה 3 כפתורים וליד כל כפתור מונה שמראה כמה פעמים לחצו עליו. אבל זה לא נגמר פה, ואנחנו גם רוצים שמעל כל הכפתורים יהיה כתוב כמה לחיצות סך הכל נלחצו בכל שלושת הכפתורים.

דרך אחת לגשת לזה היא לבקש מקומפוננטת הכפתור שתודיע לנו כל פעם שלוחצים עליה, כדי שהקומפוננטה הראשית תוכל לעדכן איזושהו מונה גלובאלי. כלומר להשתמש בטמפלייט ראשי שנראה בערך כך:

<template>
  <p>Total Clicks: {{ totalClicks }}</p>
  <Counter @click="addClicks" />
  <Counter @click="addClicks" />
  <Counter @click="addClicks" />
</template>

אנחנו יודעים שלאלמנט מסוג button אפשר להעביר מאפיין @click כדי לגרום לו להפעיל פונקציה שלנו כל פעם שלוחצים עליו, אבל איך אפשר לעשות את אותו טריק עם קומפוננטה שלנו? מסתבר שזה די פשוט:

  1. בקומפוננטה שלנו אני מגדיר שדה בשם emits ושם כותב את כל האירועים שהקומפוננטה מדווחת עליהם.

  2. כשצריך לדווח על אירוע אני מפעיל את הפונקציה this.$emit, מעביר לה את שם האירוע ואת הפרמטרים שמצורפים אליו.

במקרה של Counter הקוד יראה בערך כך:

export default {
  emits: ["click"],
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    inc() {
      this.count += 1;
      this.$emit("click", this.count);
    },
  },
};

אתם יכולים לבחור כל שם לאירוע שלכם, ולהגדיר כמה אירועים שאתם רוצים. בזכות סימן ה @ לפני שם המאפיין בטמפלייט, ויו יודע שהערך הוא פונקציה ויודע לחבר אותה לאירוע שכתבתם.

זה המקום לשאול: למה צריך שני מנגנונים שונים להעברת מידע, לפי כיוון המידע? למה כשמעבירים מידע מקומפוננטה חיצונית לקומפוננטה פנימית אנחנו מעבירים ערך באמצעות Props, אבל כשרוצים להעביר מידע מקומפוננטה פנימית לחיצונית אנחנו צריכים להפעיל פונקציה? התשובה לא נובעת ממגבלה טכנית אלא מבחירת ארכיטקטורה. העברת המידע ב Vue מ"למעלה למטה" מכריחה אותנו לחשוב לגבי כל פיסת מידע באפליקציה מי הקומפוננטה שאחראית על מידע זה, ומכריחה אותנו לשמור מידע משותף לכמה קומפוננטות בקומפוננטה עליונה יותר בעץ. מבנה כזה מאפשר יותר בקלות לזהות מהו המידע של היישום ולעקוב אחריו כדי למצוא באגים בתוכנית.

מוזמנים לחטט בקוד המלא בקישור: https://codesandbox.io/s/elastic-dawn-ch4vb.

4. מנגנונים נוספים ששווה להכיר

ב Vue קיימים עוד שלושה מנגנונים להעברת מידע בין קומפוננטות עליהם לא כתבתי בפרק זה של המדריך:

  1. מנגנון אחד פשוט הוא העברת מאפייני HTML שפשוט יודבקו לאלמנט הראשי בקומפוננטה הפנימית. אתם יכולים לנסות להעביר כל מאפיין שתרצו מקומפוננטה עליונה לאלה שבתוכה ותראו ב Inspect Element שהמאפיינים הודבקו לאלמנט הראשי בקומפוננטה הפנימית. מנגנון זה עוזר כשרוצים להעביר דברים שיש להם השפעה ב HTML למשל id או class.

  2. מנגנון שני נקרא provide/inject והוא מאפשר להעביר מידע בין שתי קומפוננטות הרחוקות אחת מהשניה בעץ הקומפוננטות. באופן רגיל אם יש קומפוננטה A ובתוכה קומפוננטה B ובתוכה קומפוננטה C ובתוכה קומפוננטה D, אז בשביל להעביר מידע מ A ל D צריך להעביר אותו קודם מ A ל B, ואז מ B ל C ואז מ C ל D. מנגנון provide/inject מאפשר "לדלג" על הקומפוננטות שבאמצע ולשלוח מידע מקצה אחד של העץ לקצה שני שלו.

  3. מנגנון שלישי נקרא Slots והוא מאפשר לקומפוננטה לקבל קומפוננטות אחרות כקלט באמצעות העברת "ילדים" בתוך התבנית.

אתם יכולים לקרוא על שלושת המנגנונים בתיעוד של Vue ולפחות על השניים האחרונים אני כנראה אכתוב בפרקים הבאים של מדריך זה.

5. תרגילים

אתם עוד פה? מעולה! הנה כמה רעיונות לתרגולים שתוכלו לנסות עם הדברים החדשים שלמדתם על Vue:

  1. כתבו קומפוננטה לייצוג שדה מידע בטופס - הקומפוננטה תקבל טקסט עבור ה label ושם של שדה המידע ותציג תיבת קלט עם מאפיין name שמתאים לשם שהתקבל ו label עם הטקסט שהתקבל.

  2. כתבו קומפוננטה של טופס שמציגה מספר שדות תוך שימוש בקומפוננטת השדה בטופס שכתבתם.

  3. אפשרו לקומפוננטת הטופס להעביר וולידציות שונות פנימה לקומפוננטות השדות בטפסים - למשל וולידציה שאומרת שהשדה חייב לקבל ערך, או וולידציה שאומרת שהשדה צריך להיות באורך מסוים.

  4. הוסיפו וולידציה שתוודא שמידע בשדה מסוים זהה למידע בשדה אחר (לדוגמה עבור שדה Email Validation). איזה שינויים תצטרכו לעשות בקוד בשביל לבנות וולידציה כזו?

  5. הוסיפו בראש הטופס כיתוב שמראה כמה שגיאות יש בטופס, כלומר כמה שדות לא עוברים וולידציה.

  6. קראו את התיעוד על ספריית Formulate ובנו באמצעותה את הטופס עם הוולידציות. השוו לקוד שאתם בניתם. מיטיבי לכת מוזמנים להוסיף פיצ'רים שאהבתם מתוך הספריה.