מדריך Vue למתחילים - חלק 5 - בואו נבנה נגן YouTube ב Vue

ספריית Vue היא ספריית JavaScript לפיתוח מבוסס קומפוננטות. היא נחשבת לאחת מספריות הפיתוח הפופולריות היום יחד עם React ו Angular. מה שמייחד את Vue הוא המאמץ של כותביה לבנות ממשק שיהיה מאוד ידידותי לאנשים חדשים ויאפשר כניסה קלה לעולם של קומפוננטות.

פרק זה הוא הפרק החמישי במדריך. אלה החלקים הקודמים:

  1. פרק 1 - פיתוח קומפוננטה ראשונה

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

  3. פרק 3 - תבניות דינמיות

  4. פרק 4 - ממשק ההרכבה Composition API

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

1. התקנת Vue ופיתוח פרויקט חדש על המכונה שלי

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

בשביל להתחיל פרויקט Vue מאוד נוח להתקין את כלי הפיתוח שלהם לסביבת ה CLI. בהנחה שיש לכם Node.JS מותקן תצטרכו להפעיל משורת הפקודה:

$ npm install -g @vue/cli

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

$ npx vue create video-player

הפעלה כזאת תציג תפריט בו תוכלו לבחור את סוג הפרויקט. אני בוחר פרויקט Starter עם Vue 3, Babel, eslint ו jest.

בתוך תיקיית הפרויקט הקובץ הראשון שמעניין אותנו הוא package.json והבלוק המעניין בו הוא בלוק scripts:

  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit",
    "lint": "vue-cli-service lint"
  },

ויו יצר 4 סקריפטים: הפקודה serve מפעילה שרת למצב פיתוח, הפקודה build בונה את הפרויקט לתיקיה מקומית (dist), הפקודה test:unit מריצה בדיקות יחידה ו lint בודקת סטטית את הקוד. הפעילו:

$ yarn serve

ותוכלו להיכנס בדפדפן למחשב המקומי בפורט 8080 כדי לראות את דף הפתיחה.

2. התקנת ספריית youtube-player באמצעות yarn

אנחנו רוצים לבנות נגן וידאו ובעצם לעטוף ספריית JavaScript קיימת שנקראת youtube-player. צעד ראשון יהיה להתקין את הספריה. בעבודה עם פרויקט Vue מסוג Starter אנחנו מתקינים חבילות JavaScript מ npm. החבילות יישמרו בתיקיית node_modules בתוך תיקיית הפרויקט ובעת הבניה אלה מהן שנמצאות בשימוש יועתקו לתיקיית dist. נתקין את הספריה youtube-player באמצעות הפקודה:

$ yarn add youtube-player

ותוכלו לראות שהספריה הצטרפה לרשימת התלויות בקובץ package.json וגם ניתן למצוא את הקוד שלה בתיקיית node_modules שבתוך תיקיית הפרויקט.

3. כתיבת הקוד לקומפוננטת Youtube Player

ניכנס לקוד הפרויקט, נמחק את הקובץ HelloWorld.vue ובמקומו ניצור קובץ חדש בשם YoutubePlayer.vue. עכשיו מתחיל הכיף.

בשביל להשתמש בספריית youtube-player אני צריך:

  1. לטעון אותה באמצעות import

  2. ליצור אוביקט YoutubePlayer ולהעביר אליו אלמנט מה DOM

  3. כשארצה לטעון סרט למצוא את אוביקט ה YoutubePlyaer שיצרתי ולהפעיל את הפונקציה loadVideoById שלו.

המשימה הראשונה היא הפשוטה ביותר. בתחילת בלוק הסקריפט אני יכול לכתוב את הפקודה:

import YouTubePlayer from 'youtube-player';

ועכשיו העניינים מתחילים להסתבך. ספריית youtube-player רוצה שאעביר אליה אוביקט DOM, אבל קוד Vue שאני כותב עובד על "מידע" ועל "משתנים", והאחריות על הפיכת הטמפלייט ל DOM היא אצל ויו. לשמחתנו יש לי גישה למנגנון זה בצורת מנגנון שנקרא Template Refs, וזה עובד ככה: אני כותב בתוך אלמנט בטמפלייט מאפיין ref ומעביר בתור ערך שם של משתנה. ויו בתורו ישמור בתוך המשתנה הזה את אלמנט ה DOM שמתאים לאלמנט בטמפלייט שיש לו ref.

בקוד זה אומר שאם בטמפלייט אני רושם:

<template>
  <p>A Player</p>
  <input type="text" v-model="videoId" />
  <p>Loading {{videoId}}</p>
  <div ref="playerRef"></div>
</template>

אז אני יכול בתוך קוד הסקריפט לגשת לאלמנט DOM שמתאים ל div האחרון, ויש לי שתי דרכים לגשת אליו. למי שעובד ב Options API, כלומר בממשק שבו מגדירים אוביקט שיש לו שדה data ושדה methods עבור הקומפוננטה, אז אפשר לגשת לאלמנט דרך המשתנה this.$refs.playerRef בתוך המתודות של הקומפוננטה. מי שעובד ב Composition API צריך להגדיר משתנה בשם playerRef, לשמור בתוכו ערך שחוזר מפונקציה בשם ref ולהחזיר אותו באוביקט שמחזירים מ setup.

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


export default {
  name: 'YoutubePlayer',
  setup() {
    const playerRef = ref(null);

    return {
      playerRef,
    }
  }
};

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

במקום זה אני צריך להגיד ל Vue לעשות משהו אחרי שהקומפוננטה כבר על המסך ואחרי שיש לי אלמנט DOM ביד שאפשר לעבוד איתו. טיפול באירועים ב Vue הוא גם דבר שמקובל לעשות בתוך Composition API ולכן אני שמח שבחרתי בשיטת עבודה זו. האירוע המתאים נקרא mount והדרך לטפל בו היא להפעיל פונקציה של Vue שנקראת onMounted ולהעביר לה בתור פרמטר פונקציה שלי שתיקרא כדי לטפל באירוע mount.

הקוד הבא מצייר נגן וידאו על האלמנט playerRef בעקבות אירוע mount:

import YouTubePlayer from 'youtube-player';
import { ref, onMounted } from 'vue';

export default {
  name: 'YoutubePlayer',
  setup() {
    const playerRef = ref(null);
    let player;

    onMounted(() => {
      player = YouTubePlayer(playerRef.value);
      console.log(player);
    });

    return {
      playerRef,
    }
  }
};

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

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

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

    watch(
      () => [videoId.value, player],
      () => {
        if (player) {
          player.loadVideoById(videoId.value);
        }
      });

בעברית זה אומר: אם משתנה videoId.value או משתנה player משנה את ערכו, הפעל את הפונקציה שתבדוק אם player אינו null אז תנסה לטעון את הוידאו שהמזהה שלו שמור ב videoId.

סך הכל הקומפוננטה המלאה נראית כך:

<template>
  <p>A Player</p>
  <input type="text" v-model="videoId" />
  <p>Loading {{videoId}}</p>
  <div ref="playerRef"></div>
</template>

<script>
import YouTubePlayer from 'youtube-player';
import { ref, onMounted, watch } from 'vue';

export default {
  name: 'YoutubePlayer',
  setup() {
    const playerRef = ref(null);
    const videoId = ref('');
    let player;

    onMounted(() => {
      player = YouTubePlayer(playerRef.value);
    });

    watch(
      () => [videoId.value, player],
      () => {
        if (player) {
          player.loadVideoById(videoId.value);
        }
      });

    return {
      playerRef,
      player,
      videoId,
    }
  }
};
</script>

<style scope="yes">
</style>

ומילה אחרונה על פעולת ה .value. שני המשתנים playerRef ו videoId הוגדרו באמצעות המילה השמורה ref. פונקציה זו של vue יוצרת משתנים שיכולים לשנות את ערכם בצורה ריאקטיבית, כלומר שקוד אחר יוכל לפעול כשמשתנים אלה משנים את ערכם. אבל בשביל שזה יעבוד ויו צריך דרך לשנות את "הערך" של המשתנים בלי לשנות את המשתנים עצמם, כלומר בלי שהם יעברו להצביע על אוביקט אחר. זאת המטרה של ה value. לכל אוביקט מסוג ref יש שדה value. כתיבה ל value מעדכנת את כל מי שצריך שיש ערך חדש בשדה וגורמת להפעלת חישוב מחדש על כל הערכים שתלויים בשדה זה, או להפעלת כל פונקציות ה watch שמתאימות לשדה. קריאה מ value מחזירה את הערך של האוביקט. הקריאה דרך value הכרחית כשאנחנו קוראים את הערך מתוך קוד Vue, ונעשית בצורה אוטומטית כשקוראים את הערך מתוך התבנית. בגלל זה שורה זו מתוך התבנית:

<p>Loading {{videoId}}</p>

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

player.loadVideoById(videoId.value);

4. עכשיו אתם

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

לאחר מכן נסו לעדכן את הקוד כדי שה input הראשון (של הקידומת) יתפוס את הפוקוס איך שהעמוד עולה.

ושאלה לסיום - בקוד שכתבתי משתנה videoId הוגדר להיות ref. איזה חלק בקוד לא היה עובד אם משתנה זה היה מוגדר בתור מחרוזת רגילה?