מדריך Vue למתחילים - חלק 3 - תבניות דינמיות

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

  1. חלק 1 - קומפוננטה ראשונה ב Vue

  2. חלק 2 - תקשורת בין קומפוננטות

1. מה אנחנו רוצים לבנות

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

  1. תבנית המציגה רשימת מטלות לביצוע, כשהמטלות עצמן מגיעות משרת או מפעולות של משתמש.

  2. תבנית של קומפוננטה שלפעמים מציגה כפתור Login ולפעמים כפתור Logout, תלוי אם המשתמש מחובר למערכת.

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

2. מה לא עושים

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

<p>Hello {{ name }}</p>

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

<template>
<ul>
  {{ 
    ['one', 'two', 'three'].map(text => <li>{{text}}</li>)
  }}
</ul>
</template>

אבל ויו לא משתף פעולה. הודעת השגיאה שתופיעה היא:

VueCompilerError: Invalid end tag.
at /src/components/HelloWorld.vue:4:53
2  |  <ul>
3  |    {{ 
4  |      ['one', 'two', 'three'].map(text => <li>{{text}}</li>)
   |                                                      ^
5  |    }}
6  |  </ul>

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

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

3. גישה 1: שימוש ב Directives

הגישה הראשונה של Vue להכנסת דינמיות לתוך התבניות היא שימוש במילות קוד מיוחדות של Vue:

  1. ההוראה v-for מייצרת לולאה

  2. ההוראה v-if מייצרת תנאי

  3. ההוראות v-else ו v-else-if

בואו נראה מה התפקיד של כל אחת מהן ואיך להשתמש בה. נתחיל עם v-for. אם אנחנו רוצים לייצר לולאה בתוך תבנית, נוסיף מאפיין v-for לאלמנט שאנחנו רוצים לשכפל. הערך של המאפיין יהיה ביטוי מהצורה x in y או (x, index) in y או x in N כאשר x הוא שם של משתנה רץ ו y הוא שם של מערך. בצורה השניה index הוא שם של משתנה שמקבל את אינדקס האיבר, ובצורה השלישית N הוא מספר והלולאה רצה N פעמים.

את אותה תבנית שלא הצלחתי לכתוב קודם אפשר לכתוב בקלות עם v-if בצורה הבאה:

<template>
  <ul v-for="text in ['one', 'two', 'three']" :key="text">
    <li>{{ text }}</li>
  </ul>
</template>

עכשיו צריך לעצור רגע על המאפיין שהוספתי :key. תפקידו לעזור ל vue להבין אם אלמנטים שינו מקום בין הצגה אחת של המידע לזו שאחריה, והוא צריך להחזיק ערך ייחודי שמתאים לאלמנט הנוכחי בלולאה. אם אתם בטוחים שאלמנטים אף פעם לא ישנו מקום (למשל כי אין לכם פונקציית מיון בתוכנית) תוכלו להשתמש באינדקס, אם הערכים ייחודיים כמו בדוגמה שאני כתבתי תוכלו להשתמש בערך עצמו, ואם יש לכם אוביקטים אז אולי יש לכל אוביקט שדה id. בכל מקרה זאת אחריות שלכם למצוא מזהה ייחודי לכל פריט שאתם רוצים להציג ברשימה ולהעביר אותו למאפיין key.

הנה לולאה נוספת הפעם שתציג את כל המספרים מ-1 עד 100:

<template>
  <div>
    <p v-for="number in 100" :key="number">{{number}}</p>
  </div>
</template>

אפשר להוסיף v-for לכל אלמנט בטמפלייט מלבד האלמנט החיצוני ביותר, וזה פשוט גורם ל vue לשכפל את האלמנט לפי הלולאה שנכתוב.

דרך נוספת לקבל דינמיות היא להציג או להסתיר אלמנט מסוים, ואת זה אנחנו עושים עם v-if, v-else ו v-else-if. הקומפוננטה הבאה תציג את המילה Happy אם הטקסט שכתבתם בתיבה מתחיל באות קטנה, ו Sad אם הטקסט מתחיל באות גדולה:

<template>
  <div>
    <label>
      Type Something
      <input type="text" v-model="text" />
    </label>
    <div>
      <p>
        My Mood today is:
        <span class="mood-happy" v-if="mood(text) === 'Happy'">Happy</span>
        <span class="mood-sad" v-else-if="mood(text) === 'Sad'">Sad</span>
      </p>
    </div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      text: "",
    };
  },
  methods: {
    mood(text) {
      if (text.length === 0) {
        return "Sad";
      } else if (text[0].toUpperCase() === text[0]) {
        return "Happy";
      } else {
        return "Sad";
      }
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.mood-happy {
  color: pink;
}

.mood-sad {
  color: blue;
}
</style>

החלק הדינמי בתבנית הוא שני ה span-ים עם המאפיינים v-if ו v-else-if, שיוצגו רק אם התנאים שלהם מתקיימים.

אתם מוזמנים לקרוא את כל הפרטים על v-if בתיעוד בקישור: https://v3.vuejs.org/guide/conditional.html

ואת כל הפרטים על v-for בתיעוד בקישור: https://v3.vuejs.org/guide/list.html

4. גישה 2: שימוש ב Render Functions

גישה שניה לכתיבת קומפוננטות דינמיות היא לוותר לגמרי על בלוק ה template ולהחליף אותו בפונקציה בשם render שמחזירה את עץ הקומפוננטות הוירטואלי בצורה דינמית.

הנה דוגמה לקומפוננטה סופר פשוטה שמשתמשת בגישה זו:

<script>
import { h } from "vue";

export default {
  name: "HelloWorld",
  render() {
    return h("p", "Hello World");
  },
};
</script>

קומפוננטות כאלה אנחנו אפילו לא חייבים לכתוב בקובץ עם סיומת vue ויכולים לכתוב אותן בקובץ עם סיומת js רגיל.

הפונקציה render מחזירה "עץ קומפוננטות" שנבנה באמצעות הפונקציה h של vue. הפונקציה h מקבלת שלושה פרמטרים: שם האלמנט, הילדים ומאפייני האלמנט, ובונה את העץ. הנה דוגמה יותר מתוחכמת לשימוש בה, הפעם עם לולאה:

<script>
import { h } from "vue";

export default {
  name: "HelloWorld",
  render() {
    return h(
      "ul",
      ["one", "two", "three"].map((text) => h("li", { key: text }, text))
    );
  },
};
</script>

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

export default {
  name: "HelloWorld",
  render() {
    return (
      <ul>
      {["one", "two", "three"].map(text => (
        <li key={text}>{text}</li>
      ))}
      </ul>
    );
  },
};

ברוב המקרים ב Vue נשתמש בגישת הטמפלייטס לרוב הקומפוננטות שלנו, אבל אם נגלה שקומפוננטה מסוימת צריכה יותר גמישות אז תמיד אפשר לעבור לכתוב Render Function.

5. סיכום

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

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

6. תרגילים

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

  1. כתבו קומפוננטה בשם FilteredList שמציגה רשימה של פריטים שקיבלה מבחוץ (באמצעות prop) ותיבת חיפוש. הקלדה של טקסט בתיבת החיפוש מצמצמת את הפריטים שמופיעים על המסך ומראה רק את אלה שמתאימים למילה שהוקלדה בתיבת החיפוש.

  2. כתבו קומפוננטה בשם RandomOrder שמציגה רשימה של פריטים שקיבלה מבחוץ וכפתור Shuffle. לחיצה על כפתור Shuffle מערבבת את הפריטים ומציגה אותם בסדר חדש.

  3. כתבו קומפוננטה בשם SelectableList המציגה רשימה של פריטים שקיבלה מבחוץ וליד כל פריט מציגה Checkbox, וליד הרשימה כפתור Delete. משתמשים יוכלו לבחור מספר פריטים וללחוץ Delete כדי למחוק אותם. הוסיפו גם כפתור Reset שמחזיר את כל הפריטים שנמחקו.

  4. הוסיפו לקומפוננטה SelectableList כפתור Shuffle שיערבב את סדר הפריטים. וודאו שאתם יכולים לסמן פריטים, לערבב, ומה שסימנתם נשאר מסומן.

  5. כתבו קומפוננטה בשם SortableTable המציגה מידע בצורה של טבלה עם אפשרות למיין את המידע לפי העמודות. לדוגמה אם המידע שלי הוא:

const data = [
    ['id', 'Name', 'Country', 'Email'],
    [0, 'dan', 'Israel', 'dan@gmail.com'],
    [1, 'dana', 'Israel', 'dana@gmail.com'],
    [2, 'anna', 'Israel', 'anna@gmail.com'],
    [3, 'zina', 'UK', 'zina@gmail.com'],
];

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