מדריך Vue למתחילים - חלק 5 - בואו נבנה נגן YouTube ב Vue
ספריית Vue היא ספריית JavaScript לפיתוח מבוסס קומפוננטות. היא נחשבת לאחת מספריות הפיתוח הפופולריות היום יחד עם React ו Angular. מה שמייחד את Vue הוא המאמץ של כותביה לבנות ממשק שיהיה מאוד ידידותי לאנשים חדשים ויאפשר כניסה קלה לעולם של קומפוננטות.
פרק זה הוא הפרק החמישי במדריך. אלה החלקים הקודמים:
בפרק היום נראה איך לעטוף קוד קיים בתור קומפוננטת 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 אני צריך:
לטעון אותה באמצעות import
ליצור אוביקט YoutubePlayer ולהעביר אליו אלמנט מה DOM
כשארצה לטעון סרט למצוא את אוביקט ה 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. איזה חלק בקוד לא היה עובד אם משתנה זה היה מוגדר בתור מחרוזת רגילה?