ch
Feedback
ToCode

ToCode

前往频道在 Telegram

טיפים קצרים למתכנתים מאת ינון פרק

显示更多
1 419
订阅者
无数据24 小时
-17
+230
帖子存档
ToCode
1 419
# האבסורד של טכנולוגיות נגישות מחוץ לעולם הטכני של נגישות אתרים, אפשר לדבר על פריימוורק "נגיש" בתור פריימוורק שקל לאמץ אותו, משהו שהוא נגיש עבורנו. יש כל מיני מאפיינים שהופכים ספריה או פריימוורק לנגישים- תקופה ארוכה PHP היה נגיש כי מספיק היה לשים קובץ PHP על השרת כדי לקבל אתר דינמי (בניגוד ל setup המסורבל של CGI Scripts שהיו לפניו). jQuery היה נגיש כי היה מאוד קל למתכנתים להבין איך להשתמש בו ולבנות קוד שעובד. ריילס ב 2005 היה מאוד נגיש כי הוא דיבר לאותם מאוכזבי PHP שחיפשו יותר סדר בקוד שלהם, אבל לא הרגישו בנוח לעבור ל Java או .NET, אנגולר1 היה יותר נגיש מ Backbone ו Ember למתכנתי Backend כי הוא איפשר להם "לדלג" על לימוד עולם ה Front End ולכתוב דברים סבירים בלי להבין את המכניקה שמתחת, והיום Vue יותר נגיש מ React או Angular מסיבות דומות. האבסורד הוא שכמעט לכל טכנולוגיה נגישה מגיע בסוף רגע "ההתפכחות", שבו אנחנו מבינים שרק בגלל שהיה קל להתחיל לעבוד בטכנולוגיה מסוימת לא בהכרח הופך אותה לטובה. ב Angular1 זה היה עם הפופולריות של ג'ון פאפא וה Style Guide המפורסם שלו. ריילס הפכה למסורבלת באזור 2010 ככל שעלתה הפופולריות של Node.JS ומיקרו פריימוורקס בצד שרת ו jQuery הפסיקה להיות אטרקציה כשמתכנתים "רציניים" התחילו להביא לעולם ה Front End רעיונות של תכנות מונחה עצמים ואנקפסולציה שהם הכירו מסביבות אחרות, במיוחד החל מ 2008 עם הספר JavaScript The Good Parts. ההתפכחות לא מגיעה בגלל שהטכנולוגיה משתנה אלא בגלל שהדרישות, היישומים והמשתמשים השתנו. צוותים גילו שאת המוצר שהם כתבו בקלות ב Angular.JS או ריילס הם כבר לא מצליחים לתחזק אחרי שנתיים של פיתוח. האבסורד הוא שדווקא בגלל שהיה כל כך קל להתחיל להשתמש בטכנולוגיה X, לא עצרנו לחשוב איך יהיה נכון להשתמש בה ואיך לבנות ארכיטקטורה שתחזיק מעמד לאורך שנים.

ToCode
1 419
1. שימוש ב Programmatic Navigation כדי לנווט בצורה יזומה כשמשתמש לוחץ על הכפתור להמשך. 2. שימוש ב Navigation Guards כדי למנוע ממשתמש להגיע לדף המשחק אם שם המשתמש הוא ריק. נסו לבנות את הפיתרון בכל אחת משתי הדרכים כדי לראות איזו דרך אתם מעדיפים.

ToCode
1 419
          <li v-for="n in 10" :key="`item-${n}`"><router-link :to="`/products/${n}`">Product {{n}}</router-link></li>
        </ul>
      </nav>
      <router-view />
    </div>
  </div>
</template>

<script>
  export default {
  }
</script>

<style scoped>
.buy-page {
  padding: 1rem;
}

.container {
  display: flex;
}

nav { 
  margin-right: 2rem;
}

ul {
  list-style: none;
  padding: 0;
}

</style>

הקובץ המלהיב באמת הוא ProductPage.vue:
<template>
  <div v-if="data">
    <p>Product page for {{ data.name }}</p>
  </div>
  <div v-else>
    Loading...
  </div>
</template>

<script>
import { useSWR } from 'vswr'
import { useRoute } from 'vue-router'

export default {
  name: 'ProductPage',
  setup() {
    const route = useRoute()
    const { data, error } = useSWR(() => `https://swapi.dev/api/people/${route.params.id}`);

    return {
      data,
      error,
    }
  }
}
</script>

<style scoped>
</style>
קוד לא ארוך אבל עושה מספר דברים מעניינים. תחילה בתוך פונקציית setup הפונקציה useRoute מחזירה את "הנתיב" הנוכחי. דרך אוביקט הנתיב אני יכול לקבל את הפרמטר id באמצעות הקריאה route.params.id. שנית הפונקציה useSWR עליה כבר דיברתי באחד הפרקים הקודמים במדריך זה, מקבלת פונקציה שמחזירה URL ומושכת את המידע מאותו URL. ברגע שיהיה שינוי ב route.params.id הפונקציה תופעל שוב ותמשוך את המידע החדש. במבנה כזה אנחנו מושכים את המידע מהשרת אחרי שהקומפוננטה ProductPage נכנסה לעמוד ולכן בתבנית של הקומפוננטה אני מטפל גם במקרה בו המידע עדיין לא זמין. זה ה if/else שמופיע שם שמציג את המילה Loading אם data עדיין ריק. השינוי האחרון הוא התוספת:
<router-link to="/products">Shop</router-link>
בתפריט העליון בקומפוננטה App.vue. נ.ב. אתם יכולים לשים לב שהוספתי גם קובץ CSS בשם assets/main.css. בעבודה עם vue אני מעדיף להוסיף קובץ CSS מסודר עבור כללי CSS גלובאליים שמשפיעים על כל הדפים, ולהשתמש בהגדרות ה style בתוך קבצי vue בשביל כללי עיצוב שרלוונטים לקומפוננטה מסוימת. ## נתיבים עם שמות טריק אחרון של Vue Router שאני רוצה להראות הוא התמיכה בניווט לפי שמות. שימו לב לפקודות ה router-link בטמפלייט שלנו:
<router-link to="/contact">Contact Us</router-link>
אם מחר אני ארצה לשנות את מבנה הנתיב, למשל לשנות את ה URL שיהיה /contact-us אני אצטרך ללכת לכל router-link שמוביל לנתיב זה ולשנות אותו, ובמערכת גדולה זה עלול להיות ארוך ומועד לטעויות. טריק נחמד שאפשר לעשות במקום הוא להוסיף מאפיין name בהגדרת הנתיב באופן הבא:
{ path: "/contact", component: Contact, name: "contact" },
ואז ב router-link אני יכול להשתמש באותו name שהוא מזהה פנימי שלא קשור ל URL. קוד ה router-link שישתמש ב name יהיה:
<router-link :to="{ name: 'contact' }">Contact Us</router-link>
בנתיב עם פרמטרים, לדוגמה בקישור לדף מוצר, הקוד יראה כך:
<router-link :to="{ name: 'product', params: { id: 7 } }">Product 7</router-link>
מבנה כזה מאפשר לי לשנות את ה URL-ים בלי שאצטרך לרוץ לשנות בהתאמה את כל הלינקים בכל המערכת. ## עכשיו אתם בתרגיל הפעם אני רוצה לשלוח אתכם ללמוד משהו חדש על ה Router ולשלב את זה עם פיתוח משחקון ב Vue: 1. כתבו משחק זיכרון ב Vue. המשחק מציג 10 ריבועים אפורים, כל אחד מייצג קלף והקלפים ממוספרים מ-1 עד 5. לחיצה על קלף הופכת אותו וחושפת את המספר שבו. אחרי הלחיצה על קלף נוסף אם לשני הקלפים אותו מספר שניהם נשארים גלויים, ואם מספרים שונים שניהם יוסתרו. 2. אחרי שיש לכם משחק זיכרון נמשיך לשילוב ה Router. כתבו מסך פתיחה הכולל תיבה לבחירת שם משתמש וכפתור "התחילו משחק". לחיצה על הכפתור אחרי בחירת שם משתמש מתחילה משחק. 3. במהלך המשחק נספור לשחקן כמה זמן לקח להפוך את כל הקלפים - כלומר להגיע לניצחון. את הזמן נשמור בטבלת שיאים. 4. הוסיפו למסך הפתיחה קישור לטבלת השיאים שמראה את 5 השחקנים שסיימו את המשחק הכי מהר. מטבלת השיאים אפשר לחזור למסך הפתיחה אבל לא להתחיל משחק חדש. בשביל לפתור את התרגיל תצטרכו לגלות איך לעבור ממסך הפתיחה למסך המשחק רק אם הגולש בחר שם משתמש, אבל לא לאפשר מעבר אם שם המשתמש ריק. ב Vue Router ניתן לפתור את הבעיה במספר דרכים:

ToCode
1 419
במצב עניינים רגיל פרמטר שקומפוננטה מקבלת עובר אליה בתור Prop; אבל כשהקומפוננטה נמצאת בתור נתיב ב Router הפרמטר עובר בצורה קצת שונה. בשביל לראות את זה נוסיף לאתר שלנו דף של חנות מרצ'נדייז שמוכרת בובות של מלחמת הכוכבים. כל "מוצר" יהיה בסך הכל דף עם שם של דמות אחרת מהסרט. לפני המעבר לפרודקשן נצטרך להוסיף עיצוב, מחיר ותיאור לכל בובה - אבל בינתיים ובשביל הדוגמה שם הדמות יספיק. אם נדמיין את מבנה הנתיבים אפשר לדמיין נתיב ראשי של החנות /products, ואחריו נתיבים לכל אחד מהמוצרים כמו /products/1, /products/2 ו /products/7. כל מספר מייצג מזהה של מוצר אחר. אנחנו כבר יודעים שהנתיב הראשי יהיה המחרוזת /products. לגבי נתיבי המוצרים הם נקראים נתיבים עם פרמטרים ואנחנו מתארים אותם באמצעות החלפת החלק המשתנה (המספר) במילה שתהיה שם הפרמטר עם תחילית :, כלומר במקרה של החנות יהיה לנו /products/:id. בנוסף שני הנתיבים /products ו /products/:id כוללים חלקים משותפים: בנתיב products אני מצפה לראות רשימה של כל המוצרים באיזשהו תפריט צד ולהשאיר את האזור המרכזי ריק כי כרגע אנחנו לא מסתכלים על אף מוצר; ובנתיב /products/:id, לא משנה עבור איזה id, אני מצפה לראות את אותו תפריט צד עם רשימת המוצרים אבל הפעם בחלק המרכזי את פרטי המוצר שמתאים ל id. יש מספר דרכים להציג תוכן משותף בין מספר דפים ב Vue, אבל בגלל שאנחנו מדברים כאן על Vue Router אני אראה את הדרך שמשתמשת ב Router - והיא השימוש ב children. הרעיון הוא שכל קומפוננטה יכולה להכיל תתי-נתיבים עבור מופעים של router-view שנמצאים בתוכה. כלומר אני יכול לכתוב בתוך הגדרת נתיב:
{
    path: '/products',
    component: Store,
    children: [
        { path: ':id', component: ProductPage },
    ]
}
ובתוך הקומפוננטה Store לייצר router-view, ואז אם הנתיב מתאים ל /products אותו router-view פנימי יופיע ריק; ואם הנתיב מתאים ל /products/:id (למשל אם זה /products/8) אז בתוך ה router-view תופיע הקומפוננטה ProductPage שתקבל את הפרמטר id. נמשיך לכתיבת הקוד לחנות ולמוצרים ואחר כך הסבר עליו. אתם מוזמנים כבר לראות את התוצאה בקודסנדבוקס כאן: <iframe src="https://codesandbox.io/embed/vue-router-demo-1-forked-e2nq9?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="vue-router-demo-1 (forked)" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" ></iframe> או בקישור: https://codesandbox.io/s/vue-router-demo-1-forked-e2nq9?file=/src/assets/main.css. הקוד כולל קצת ארגון מחדש בהשוואה לדוגמה הקודמת. הוצאת את הנתיבים לקובץ routes.js כדי שלא יתערבבו עם דברים אחרים בקובץ main.js. לקובץ routes הוספתי את הנתיב products ואת הילדים שלו בנתיב /products/:id. ככה זה נראה:
import Home from "./components/Home";
import About from "./components/About";
import Contact from "./components/Contact";
import Store from "./components/Store";
import ProductPage from "./components/ProductPage";

const routes = [
  { path: "/", component: Home, name: "home" },
  { path: "/about", component: About, name: "about" },
  { path: "/contact", component: Contact, name: "contact" },
  {
    path: "/products",
    component: Store,
    name: "shop",
    children: [
      { path: "/products/:id", component: ProductPage, name: "product" }
    ]
  }
];

export default routes;
בשלב זה אפשר להתעלם משדה name שהוספתי לכל נתיב. בדוגמה הבאה אספר לכם למה הוא טוב. הקומפוננטה Store לא כוללת הרבה תוכן חדש ובסך הכל מציגה קונטיינר עם תפריט צד שמציג כפתור ניווט לכל אחד מהמוצרים - כך נראה הקובץ Store.vue:
<template>
  <div class='buy-page'>
    <h1>Buy Some Stuff</h1>
    <p>Please find below the descriptions for our star wars action figures</p>

    <div class="container">
      <nav class="sidenav">
        <ul>

ToCode
1 419
      <router-link to="/">Home</router-link>
      <router-link to="/about">About Us</router-link>
      <router-link to="/contact">Contact Us</router-link>
    </nav>
    <main>
      <router-view />
    </main>
  </div>
</template>

<script>
export default {
  name: 'App',
}
</script>

<style>

html, body {
  padding: 0; margin: 0;
}

nav {
  padding: 1rem;
  padding-left: 0.1rem;
  background: #595959;
}

nav a {
  margin: 0.2rem;
  text-decoration: none;
  background: #99aabc;
  padding: 0.2rem 0.5rem;
  width: 5rem;
  text-align: center;
  display: inline-block;
  color: #2211f1;
}

nav a::visited {
  color: #2211f1;
}

nav a.router-link-active {
  background: yellow;
}

</style>

את התוצאה אפשר לראות בקודסנדבוקס הבא: <iframe src="https://codesandbox.io/embed/vue-router-demo-1-cp0e7?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="vue-router-demo-1" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" ></iframe> ובקישור: https://codesandbox.io/s/vue-router-demo-1-cp0e7?file=/src/App.vue. הנקודה הראשונה היתה הגדרת הנתיבים. שימו לב לקטע הבא מהקובץ main.js:
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/contact', component: Contact },
];
הקטע מגדיר מערך של 4 אוביקטים, כל אוביקט מייצג "נתיב". לנתיב יש את ה URL שלו ואת הקומפוננטה שתופיע כשמשתמש ייכנס לנתיב. בשביל לאתחל את האפליקציה עם הנתיבים אני משתמש בקוד:
const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes,
});

createApp(App)
  .use(router)
  .mount('#app')
הפונקציה createRouter יוצרת את אוביקט ה Router, והפונקציה use מוסיפה את ה Router ליישום. הפקודה createWebHashHistory היא זאת שבגללה כל הנתיבים מופיעים עם סימן ה #. האלטרנטיבה נקראת createWebHistory ותשתמש ב History API. בתוך הקובץ App.vue אפשר לראות שהגדרתי בתבנית את כפתורי הניווט בתור router-link:
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/about">About Us</router-link>
      <router-link to="/contact">Contact Us</router-link>
    </nav>
נכון, במקום herf אנחנו משתמשים במאפיין to, אבל חוץ מזה זה מספיק דומה ל a. בונוס נחמד שקיבלנו הוא CSS Class בשם המיוחד router-link-active שיתווסף אוטומטית ל router-link שמייצג את הדף שכרגע אני נמצא בו. קוד ה CSS הבא גורם לקישור זה לקבל רקע צהוב:
nav a.router-link-active {
  background: yellow;
}
שורה אחרונה בטמפלייט באותו קובץ App.vue היא:
<main>
    <router-view />
</main>
לתוך אלמנט זה Vue כותב את הקומפוננטה המתאימה לנתיב מבין הקומפוננטות שמופיעות בטבלת הנתיבים שלו. ## יצירת קומפוננטה אחת לטיפול בנתיב עם פרמטרים שונים אפליקציות בעולם האמיתי הרבה פעמים צריכות להציג מידע והרבה פעמים בשביל להציג מידע אנחנו משתמשים באותה תבנית, כאשר כל פעם ממלאים אותה במידע אחר. לדוגמה: 1. אתר של חנות שמציג מוצרים שונים - התבנית של דף המוצר זהה לכל המוצרים, אבל התוכן שונה בין מוצר למוצר. 2. אתר של ניהול אירועים שמאפשר להציג דף לכל אירוע. התבנית לכל אירוע זהה אבל המידע כמו שם האירוע, התאריך והמקום יהיו שונים בין האירועים. 3. אתר של בית מלון שכולל דף מידע על כל סוג חדר. תבנית דף המידע זהה אבל המידע כמו גודל החדר, התמונות והמחיר שונים בין סוגי החדרים השונים. המשותף לכל האתרים הוא שיש לנו תבנית זהה ולתוכה אנחנו כותבים כל פעם תוכן אחר. אפשר לחשוב על התבנית בתור קומפוננטה והתוכן האחר יאופיין באמצעות פרמטרים מסוימים שעוברים לקומפוננטה. לדוגמה באתר ניהול האירועים תהיה קומפוננטה להצגת פרטי אירוע שתקבל בתור פרמטר את מזהה האירוע, ותוכל באמצעות מזהה זה למשוך מהשרת את המידע על פרטי האירוע.

ToCode
1 419
אז תוכלו לייצר קישור עם סימן # שיוביל לדף ה HTML הזה ולנקודה הספציפית tasks בתוך הדף. אם לדוגמה כתבתם את תגית ה a בתוך דף HTML שנקרא demo.html אז הקישור demo.html#tasks יוביל את הגולש ישירות למקום בעמוד בו מופיע ה a, כלומר ישירות לרשימת המשימות. שינוי ה Hash היה הדרך היחידה שהיתה לנו לשנות את ה URL בלי לחייב גישה מחדש לשרת, ולכן קונספט ראשון של Single Page Application שנוצר נקרא Hash Navigation. בסכימה זו כל "דף" ביישום הוא פשוט Hash שונה בתוך HTML יחיד. כך יהיו לנו index.html#home, index.html#about, index.html#contact ואפילו index.html#products/2. בכל אחד מהמקרים הדפדפן יפנה לשרת כדי למשוך את הקובץ index.html, וקוד JavaScript יסתכל על ה Hash וימלא את התוכן שאנחנו רואים בעמוד כדי להתאים ל"דף" בו רצינו לבקר. לימים דפדפנים הוסיפו תמיכה במנגנון שנקרא History API. במנגנון זה אפשר היה כבר מתוך קוד JavaScript לשנות את ה URL בלי שנצטרך פניה לשרת, ולכן אני יכול להשתמש ב URL-ים רגילים לגמרי, וכשמשתמש רוצה לעבור דף אני פשוט מתוך JavaScript בצורה יזומה משנה את ה URL. עכשיו נוצרה בעיה חדשה שהיא צד השרת - שהיה צריך להתמודד עם כניסה לכל מיני URL-ים אפילו שאין קבצי HTML או קוד צד שרת שיודע לטפל בהם. לדוגמה יכול להיות שיש לי רק קובץ index.html על השרת ואז בטעינה הראשונה ה URL הוא index.html, אבל משתמש עובר לדף "אודות" וקוד JavaScript משנה את ה URL ל about.html (ואין קובץ כזה על השרת). עכשיו עד לפה הכל עובד כי שינוי ה URL בוצע רק בקוד JavaScript בלי פניה לשרת, אבל אם משתמש ישמור את ה URL החדש בתור סימניה או ירענן את העמוד, השרת יצטרך להתמודד עם פניה לנתיב about.html שלא קיים שם. כשאנחנו כותבים יישומי Single Page היום יש לנו לכן שתי אפשרויות: 1. אנחנו יכולים להשתמש במנגנון ה Hash הישן שעדיין עובד. ה URL יראה קצת מוזר אבל לא נדרשת שום התאמה לקוד צד השרת בשביל להשתמש בזה. 2. אנחנו יכולים לעדכן את קוד צד השרת כך שיענה לכל הבקשות (גם כאלה לדפים שהוא לא מכיר) ותמיד יחזיר את קובץ ה HTML הראשי של האפליקציה. בדוגמאות בפוסט זה בשביל לא להתעסק עם שינויים בקוד צד שרת אני אשתמש ב Hash History. אתם תראו שב Vue מאוד קל לבחור בין השניים, וכשתרגישו מוכנים לשנות את קוד צד השרת כך שגם יתאים למבנה של Single Page App תוכלו להחליף ל History API החדש יותר. ## יצירת SPA ראשון עם Vue Router אחרי כל מה שדיברנו על Single Page Applications הגיע הזמן לראות דוגמה לאחד כזה באמצעות Vue ו Vue Router. התחילו ביצירת פרויקט Vue והוסיפו לו את vue-router עם הפקודה:
yarn add vue-router@4
שימו לב שנכון לכתיבת הפוסט ברירת המחדל היא vue-router בגירסה 3 הישנה יותר ולכן אני משתמש ב @ כדי לקחת את הגירסה החדשה. בשביל לכתוב יישום Single Page עם Vue Router אני בסך הכל צריך לטפל במספר נקודות: 1. אני צריך להגדיר איזה נתיבים האפליקציה שלי מכירה 2. אני צריך לאתחל את אפליקציית ה Vue שלי ולהוסיף אליה את Vue Router עם רשימת הנתיבים 3. אני צריך להשתמש בקומפוננטה מיוחדת של vue-router שנקראת router-link כדי לנווט בין דפים 4. אני צריך להשתמש בקומפוננטה מיוחדת של vue-router שנקראת router-view כדי להציג תוכן של דף שמשתנה כשמשתמשים עוברים בין URL-ים. בואו נראה את ארבעת הנקודות בקוד. תחילה בקובץ main.js נכתוב את הקוד הבא:
import { createApp } from 'vue';
import * as VueRouter from 'vue-router';

import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
import App from './App.vue';

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/contact', component: Contact },
];

const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes,
});

createApp(App)
  .use(router)
  .mount('#app')
לאחר מכן נמשיך ליצירת הקומפוננטות: בקוד אני מתיחס ל-3 קומפוננטות בשם Home, About ו Contact. כל קומפוננטה לא צריכה לכלול יותר מדי קוד וזו דוגמה לקומפוננטת Home אצלי:
<template>
  <p>This is my homepage</p>
</template>

<script>
export default {
  name: "Home",
};
</script>
ובסיום נעדכן את הקובץ App.vue לתוכן הבא:
<template>
  <div class="container">
    <nav>

ToCode
1 419
# מדריך Vue למתחילים - חלק 6 - יישומי Single Page Apps עם Vue Router ויו היא ספריה לפיתוח יישומי צד-לקוח מורכבים. בשבועות האחרונים אני מפרסם לאט מדריך Vue למתכנתי Front End שרוצים להתחיל לעבוד עם הספריה. עד כה פורסמו הפרקים: 1. פרק 1 - פיתוח קומפוננטה ראשונה 2. פרק 2 - תקשורת בין קומפוננטות 3. פרק 3 - תבניות דינאמיות 4. פרק 4 - ממשק ההרכבה Composition API 5. פרק 5 - דוגמת פיתוח נגן YouTube ב Vue אם עברתם על כל החלקים אתם כבר צריכים להבין לא רע מה כל המהומה סביב Vue וגם מסוגלים כבר לבנות יישומים בסיסיים. בפרק היום נדבר על Vue Router ונראה מהם יישומי עמוד-יחיד ואיך Vue עוזר לנו לבנות כאלה בקלות. ## מהו Single Page App אחת המהפכות המעניינות בפיתוח ווב היה המעבר מפיתוח "אתרים" לפיתוח "אפליקציות". התהליך הזה הושפע ממספר פיתוחים מקבילים, מצד אחד שיפורים ביכולות של דפדפנים איפשרו לכתוב יישומים עם קוד JavaScript מאוד מורכב, ומצד שני ההשראה שקיבלו מפתחי ווב מעולם ה Mobile ומהפופולריות של חווית המשתמש בתוך אפליקציות בטלפונים. מה שמיוחד באפליקציות, בין אם בטלפונים או בדפדפנים, הוא שאפליקציה שומרת על קונטקסט. המעברים בין דפים באתר ווב של פעם כללו טעינה של קובץ HTML חדש ואיתו קבצי CSS ו JavaScript וגם הרצה מחדש של כל קוד ה JavaScript של העמוד. זה אומר שבין לחיצה על קישור להמשך עבודה בדף הבא אנחנו מחכים לטעינה של הדף החדש וקוטעים את הרצף. בהשוואה, באפליקציות גם פעולות בתוך דפים וגם מעבר לדפים אחרים קורים בצורה מיידית. בתוך דפדפן הקונספט של שינוי רק חלקים מהעמוד היה בשימוש עוד מאזור 2005 וזכה לשם Ajax. אבל רק מ 2010, במקביל לפופולריות של אפליקציות במובייל, מתכנתים החלו לחשוב ברצינות על שינוי העמוד כולו ב Ajax כדי לתת חווית משתמש מיידית במעברים בין דפים. שינוי זה מביא איתו גם הזדמנויות וגם אתגרים: בצד ההזדמנויות כשאני מחליף רק את תוכן הדף ב Ajax אני צריך לטעון הרבה פחות מידע מהשרת (לא צריך לטעון את כל ה HTML ואת כל הקבצים שסביבו); אבל בצד האתגרים צריך לזכור שדפדפנים לא נועדו לשיטת עבודה זו, ולכן קוד JavaScript צריך למלא תפקידים שבאופן רגיל עושה דפדפן כדוגמת אפשרות לדפדוף אחורה בדפים, אפשרות לשמירת דף מסוים מהאתר בתור סימניה, שמירה על מיקום הגלילה בטעינה מחדש של עמוד אבל גלילה לראש העמוד במעבר בין עמודים ומשימות נוספות בסגנון זה. באזור שנת 2015 פרדיגמת פיתוח Web Applications החלה לצבור פופולריות אבל אז עוד היה קשה לבנות אפליקציות כאלה כי רוב כלי הפיתוח והספריות שהיו בשימוש נועדו לבניית אתרים - בהם קובץ HTML מייצג דף באתר וגלישה לדף אחר מחייבת מעבר לקובץ HTML אחר. כשפריימוורקס פיתוח ווב החלו להופיע אחת ההבטחות שלהן היתה להפוך את תהליך פיתוח ה"אפליקציה" להרבה יותר קל ולדאוג בשבילנו לכל משימות הניווט במקום הדפדפן. בעולם של היום הרבה פעמים כבר יותר קל לכתוב Web Application מאשר "אתר" קלאסי, בגלל שכלי הפיתוח מותאמים לגישה זו. המאפיינים המרכזיים של Single Page Application הם: 1. דף HTML יחיד שלא מכיל תוכן, וקוד JavaScript שאחראי על מילוי התוכן עצמו. 2. ה URL משתנה בהתאמה לתוכן המשתנה על המסך, למרות שאין טעינה של דף HTML חדש. 3. מעבר בין דפים הוא מיידי או כמעט מיידי, וחלקים בעמוד יכולים להישאר קבועים למרות המעבר. 4. בדרך כלל עבודה בתוך Framework שדואג למלא את התפקיד של הדפדפן כשעברנו לגישה זו. ב Vue הספריה שעוזרת לנו לבנות Single Page Applications נקראת Vue Router. במדריך זה נראה איך להתקין אותה ואיך לבנות באמצעותה יישומי עמוד יחיד. ## ההבדל בין Hash Navigation ל History Navigation ביישומי עמוד יחיד ה URL משתנה יחד עם התוכן שמשתנה על המסך, אבל תקופה ארוכה ובזמן שמתכנתים כבר כתבו יישומי Single Page דפדפנים עדיין לא איפשרו לנו לשנות את ה URL בלי לטעון דף חדש. בשביל להתמודד עם זה השתמשנו ב"מעקף" שנקרא Hash. ב HTML תגית a היא קיצור של המילה anchor או עוגן. כמו שהיא מייצגת קישור היא גם יכולה לייצג את היעד שאליו מקשרים, או את העוגן על העמוד שאליו אנחנו רוצים ללכת. אם תכתבו בכל מקום בדף HTML את התוכן:
<a name="tasks">Tasks</a>

ToCode
1 419
# זה יעבוד בסוף דרך פופולרית ליצור קוד גרוע היא ליפול במלכודת הלחץ. זה עובד ככה, את עובדת על פיצ'ר ולא בטוחה עדיין שהדרך שבחרת היא הדרך הנכונה. ואז את אומרת לעצמך: "אני יכולה לכתוב את הפיצ'ר הזה כמו שצריך עם חלוקה נכונה לפונקציות ומחלקות ועם תיעוד ובדיקות יחידה, עם טיפול מסודר בשגיאות ועם כתיבת שאילתת SQL נכונה, אבל, אם אני בדרך הלא נכונה והכיוון הזה לא יביא אותי לפיתרון שעובד אז רק בזבזתי זמן, לכן עדיף לי לכתוב משהו מהר רק בשביל לראות שזה עובד ואחר כך אחזור לתקן". אחרי שהכל עובד מסתבר שכבר לא כל כך קל למצוא זמן ומוטיבציה לחזור לתקן, וככה נשארים עם פיתרונות חצי אפויים. האלטרנטיבה היא לכתוב רק קוד שאנחנו גאים בו ולפתור בעיות כשהן מופיעות - ופה יש שני יתרונות: הראשון והברור הוא שאין סיכוי להיתקע עם קוד גרוע כי הוא מראש לא נכתב. השני והיותר חשוב, הוא שלאט לאט הכתיבה של קוד טוב נהיית קלה יותר ואת לומדת לפתור בעיות טוב יותר ומהר יותר. (נבהיר: לכתוב קוד שאנחנו גאים בו זה לא אומר לפתור את כל בעיות העולם מראש ולפני שהן מופיעות. זה בסך הכל אומר לכתוב קוד שלם, לכתוב קוד שלא יפשל במעבר לפרודקשן, שלא ייפול באינסוף מקרי קצה, ושיהיה אפשר להסתמך עליו. דוגמה לקוד רע היא להשאיר את הטיפול בשגיאות או מקרי קצה לעתיד, או לוותר על וולידציה של קלט כדי לכתוב אותה יום אחד כשיהיה זמן. אלה דברים שבלעדיהם הקוד לא באמת עובד אלא רק נראה עובד; מצד שני קוד טוב יכול לכלול Duplication ולהשאיר לעתיד החלטה על אבסטרקציה מסוימת, וכמובן שלא חייבים לממש את כל הפיצ'רים במכה אחת. כלל אצבע מועיל כאן הוא לשאול, אם מחר אני עוזב את הפרויקט ויבוא בן אדם אחר להחליף אותי, מה הוא יחשוב עליי כשיקרא את הקוד).

ToCode
1 419
# איפור ואבטחת מידע הם לא הולכים ביחד, והבעיה שהאיפור רק מחמיר את המצב. נפלתי במקרה על הדף הזה היום: https://docs.npmjs.com/docker-and-private-modules זה מתוך התיעוד של npm. הדף מסביר איך ליצור קובץ .npmrc בתור Docker Image, למשל בשביל למשוך אימג' ממאגר פרטי. הם ממליצים להשתמש ב Dockerfile הבא:
FROM risingstack/alpine:3.3-v4.3.1-3.0.1
ARG NPM_TOKEN
COPY .npmrc .npmrc
COPY package.json package.json
RUN npm install
RUN rm -f .npmrc
# Add your source files
COPY . .
CMD npm start
ובתבנית הבאה עבור npmrc:
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
שימו לב לאיפור: לא כותבים את הטוקן בתוך הקוד אלא משתמשים במשתנה ומוחקים את הקובץ .npmrc אחרי שהשתמשו בו כדי להפעיל npm install. אבל כמובן שזה איפור כי אנחנו יודעים שכל פקודה שמריצים בתוך Dockerfile נשמרת בהיסטוריה של האימג'. תוכן המשתנה NPM_TOKEN יהיה גלוי לכל מי שיש לו את האימג', בדיוק כמו שהיה אם היינו פשוט שומרים את הקובץ .npmrc עם הטוקן בתוך האימג'. בשביל המשחק לקחתי את ה Dockerfile שלהם ובניתי ממנו אימג' וגם פירסמתי אותה ב Dockerhub. תראו אם תצליחו לגלות מה הערך של NPM_TOKEN בדוגמה שלי: https://hub.docker.com/layers/ynonp/dtest/latest/images/sha256-7eb4139557b15d6dd86cd5d9511863aaa03cb4271f3f799d701d5c80aa357575?context=explore&tab=layers.

ToCode
1 419
# איך אני מריץ שאילתת SQL מתוך הדפדפן? ש: איך אני מריץ שאילתת SQL מתוך הדפדפן? ת: אתה לא וזה משחק שמשנה את הכל. רק אחרי שאתה רואה את הבעיה ומבין באמת את המשמעות של "צד שרת" ו"צד לקוח" אפשר להתחיל לדבר על הפיתרונות, על שליחת השאילתה לשרת ועל כל ההשלכות של גישה זו על אבטחת מידע וביצועים ומה שלא תרצו. מגבלות טכניות הן לא משהו שצריך "לעקוף"; הן קודם כל משהו שצריך לראות. רק כשאנחנו רואים את הגבולות אנחנו במקום ליצור מערכות טובות יותר.

ToCode
1 419
ומילה אחרונה על פעולת ה .value. שני המשתנים playerRef ו videoId הוגדרו באמצעות המילה השמורה ref. פונקציה זו של vue יוצרת משתנים שיכולים לשנות את ערכם בצורה ריאקטיבית, כלומר שקוד אחר יוכל לפעול כשמשתנים אלה משנים את ערכם. אבל בשביל שזה יעבוד ויו צריך דרך לשנות את "הערך" של המשתנים בלי לשנות את המשתנים עצמם, כלומר בלי שהם יעברו להצביע על אוביקט אחר. זאת המטרה של ה value. לכל אוביקט מסוג ref יש שדה value. כתיבה ל value מעדכנת את כל מי שצריך שיש ערך חדש בשדה וגורמת להפעלת חישוב מחדש על כל הערכים שתלויים בשדה זה, או להפעלת כל פונקציות ה watch שמתאימות לשדה. קריאה מ value מחזירה את הערך של האוביקט. הקריאה דרך value הכרחית כשאנחנו קוראים את הערך מתוך קוד Vue, ונעשית בצורה אוטומטית כשקוראים את הערך מתוך התבנית. בגלל זה שורה זו מתוך התבנית:
<p>Loading {{videoId}}</p>
עבדה ממש בסדר והציגה את הערך האמיתי, אבל כשהגענו לטעון את הוידאו בקוד היינו צריכים לכתוב:
player.loadVideoById(videoId.value);
## עכשיו אתם אחד הדברים השימושיים שאפשר לעשות עם Template Refs הוא ניהול פוקוס. בשביל להעביר פוקוס מקלדת לקומפוננטה אנחנו משתמשים בפונקציה focus של הקומפוננטה. כתבו קומפוננטה שמקבלת ממשתמש מספר טלפון בשני שדות קלט, בשדה הראשון המשתמש מקליד 3 ספרות של קידומת ואחר כך באופן אוטומטי הפוקוס עובר לשדה השני כדי שהמשתמש יוכל להקליד את המשך המספר. לאחר מכן נסו לעדכן את הקוד כדי שה input הראשון (של הקידומת) יתפוס את הפוקוס איך שהעמוד עולה. ושאלה לסיום - בקוד שכתבתי משתנה videoId הוגדר להיות ref. איזה חלק בקוד לא היה עובד אם משתנה זה היה מוגדר בתור מחרוזת רגילה?

ToCode
1 419
  <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>

ToCode
1 419
# מדריך Vue למתחילים - חלק 5 - בואו נבנה נגן YouTube ב Vue ספריית Vue היא ספריית JavaScript לפיתוח מבוסס קומפוננטות. היא נחשבת לאחת מספריות הפיתוח הפופולריות היום יחד עם React ו Angular. מה שמייחד את Vue הוא המאמץ של כותביה לבנות ממשק שיהיה מאוד ידידותי לאנשים חדשים ויאפשר כניסה קלה לעולם של קומפוננטות. פרק זה הוא הפרק החמישי במדריך. אלה החלקים הקודמים: 1. פרק 1 - פיתוח קומפוננטה ראשונה 2. פרק 2 - תקשורת בין קומפוננטות 3. פרק 3 - תבניות דינמיות 4. פרק 4 - ממשק ההרכבה Composition API בפרק היום נראה איך לעטוף קוד קיים בתור קומפוננטת Vue ודרך זה נלמד לסנכרן בין העולם של Vue לעולם של פיתוח JavaScript שאינו מבוסס קומפוננטות. ## התקנת 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 כדי לראות את דף הפתיחה. ## התקנת ספריית youtube-player באמצעות yarn אנחנו רוצים לבנות נגן וידאו ובעצם לעטוף ספריית JavaScript קיימת שנקראת youtube-player. צעד ראשון יהיה להתקין את הספריה. בעבודה עם פרויקט Vue מסוג Starter אנחנו מתקינים חבילות JavaScript מ npm. החבילות יישמרו בתיקיית node_modules בתוך תיקיית הפרויקט ובעת הבניה אלה מהן שנמצאות בשימוש יועתקו לתיקיית dist. נתקין את הספריה youtube-player באמצעות הפקודה:
$ yarn add youtube-player
ותוכלו לראות שהספריה הצטרפה לרשימת התלויות בקובץ package.json וגם ניתן למצוא את הקוד שלה בתיקיית node_modules שבתוך תיקיית הפרויקט. ## כתיבת הקוד לקומפוננטת 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" />