uk
Feedback
ToCode

ToCode

Відкрити в Telegram

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

Показати більше
1 420
Підписники
Немає даних24 години
-17 днів
Немає даних30 день
Архів дописів
ToCode
1 420
# מדריך 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" />

ToCode
1 420
# חידת ריאקט: קומפוננטות, אפקטים וסדר פעולות הקוד הבא מתאר שתי קומפוננטות, שתיהן קוראות ל useEffect:
import { useEffect, useState } from "react";

export default function Page(props) {
  useEffect(function () {
    document.title = "Page";
  }, []);

  return <Content />;
}

function Content(props) {
  const [title, setTitle] = useState(document.title);

  useEffect(function () {
    setTitle(document.title);
  }, []);

  return <p>Page title = {title}</p>;
}

והשאלות: 1. איזה אפקט ירוץ קודם? 2. מה יהיה תוכן העמוד אחרי רינדור הקומפוננטה Page? 3. איך אפשר לשנות את סדר האפקטים ולקבל תוצאה שונה על העמוד?

ToCode
1 420
# פרויקט צד > יש אנשים שכותבים פרויקט צד כדי להרוויח כסף. אני מרוויח כסף כדי שאוכל לבנות פרויקט צד -- מקס רוזן, onlineornot מקס רוזן אולי לא מרוויח עדיין כסף מ online or not, ואולי לעולם לא ירוויח, אבל כבר בינתיים הוא הספיק לכתוב מוצר פעיל עם לקוחות אמיתיים בסט הטכנולוגיות שהוא בחר, התמודד עם בעיות ביצועים שנבעו מארכיטקטורה לא נכונה ותיקן אותן והגיע ל-4 לקוחות משלמים. בדרך הוא לומד גם על טכנולוגיה, גם על שיווק, גם על כתיבה ועל עוד המון היבטים אחרים של פיתוח מוצרים. אין לי ספק שלא משנה מה יקרה עם online or not בעתיד - מקס כבר קיבל הרבה מעבר למה שהשקיע. הדרך הנכונה להסתכל על פרויקט צד היא כמו תחביב או פעילות אקסטרה שאנחנו עושים כי זה נותן לנו משהו. לא כסף; משהו אחר. עכשיו אפשר להגיד "אין לי זמן לתחביבים", "יש לי כבר 5 כלבים וחתול שלוקחים לי את כל הזמן הפנוי" או "אני מתכנת כל היום בעבודה ומעדיף תחביבים אחרים". אין שום דבר רע בזה. אבל יש עוד דרכים להסתכל על זה: 1. בעבודה אני כותב דברים בטכנולוגיה X; פרויקט צד מאפשר לי לגלות טכנולוגיות חדשות. 2. בגלל שאני אוהב את הכלבים שלי אני נהנה לכתוב מוצרים שמשמחים אותם. 3. זה בטוח יותר מעניין מעוד בינג' בנטפליקס, ומשום מה כן היה לי זמן לראות את כל הסדרות האחרונות. --- תחביב. שם עצם - זכר. עִסּוּק מְסֻיָּם שֶׁאָדָם עוֹסֵק בּוֹ מִתּוֹךְ חִבָּה וְעִנְיָן בִּשְׁעוֹת הַפְּנַאי מֵעֲבוֹדָתוֹ, עִסּוּק שֶׁלֹּא לְשֵׁם פַּרְנָסָה אוֹ רְוָחִים, “הוֹבִּי”: פְּלוֹנִי בַּעַל תַּחְבִּיב שֶׁל אִסּוּף בּוּלִים. הוּא עוֹסֵק בְּצִיּוּר כְּתַחְבִּיב.

ToCode
1 420
# משימות קשות - שמתי לב שלוקח המון זמן להריץ את כל הבדיקות במערכת. אולי ננסה להריץ חלק מהבדיקות במקביל? יש כמה פריימוורקים לזה אבל כולם נראים מסובכים. נראה לי שאנסה לכתוב משהו קטן לבד. .. שלוש שעות יותר מאוחר - נראה לי שהצלחתי לבנות סקריפט קטן שמריץ כל קובץ בדיקות בתהליך אחר! ... שבועיים יותר מאוחר - נראה לי שהצלחתי להבין איך הבדיקות לא ידרסו מידע אחת של השניה! אנחנו רק צריכים שכל תהליך ייצור לעצמו בסיס נתונים עצמאי. הנה POC שמשנה קצת את ספריית הבדיקות כדי שזה יעבוד. ... שלושה חודשים יותר מאוחר - מסתבר שהיו עוד כמה רכיבים חיצוניים שהיה צריך לעקוף בגלל בדיקות שרצו במקביל ודרכו אחת על השניה. מי חשב שבדיקות יצרו קבצים, יפנו לשירותי רשת או יטענו DLL-ים שלא מטפלים טוב בריצה מקבילים. נראה לי שעכשיו המנגנון הבסיסי עובד ויצרתי Mock-ים לכל המנגנונים החשובים. עכשיו נשאר רק לעדכן את קוד הבדיקות להשתמש במנגנון החדש. זה לא אמור לקחת הרבה זמן... --- הרבה פעמים כשאנחנו מסתכלים על בעיה אנחנו מחפשים איזה פריימוורק פשוט שיוציא אותנו מהבוץ, ואז כשלא מוצאים מניחים שהעולם פשוט לא מספיק חכם או שהבעיה שלנו נורא מיוחדת. זה באמת המצב לפעמים אבל הרבה פעמים העובדה שכל הפיתרונות המוכנים מסובכים פשוט אומרת שהבעיה היא מסובכת. זה לא סוף העולם לפתור בעיות מסובכות, ויש שיגידו שזה אפילו כיף, אבל סיכוי טוב שהתוצאה של קידוד משהו קטן לבד לא תיגמר במשהו קטן. מצד שני בסוף התהליך יש סיכוי לא רע שתבינו הרבה יותר טוב למה כל הפיתרונות הקיימים כל כך מסובכים.

ToCode
1 420
# שלוש שאלות למה כתבת את השורה הזאת? - כי זאת הדרך שתמיד כתבתי כזה קוד - כי מצאתי את זה בגוגל - כי מצאתי את זה ב Stack Overflow - כי ראיתי את זה בסרט ביוטיוב - כי ראיתי מנגנון דומה במקום אחר במערכת - כי זה מה שהציע המתכנת הבכיר - כי זה מה שהציע החבר לידי - כי זה עבד עכשיו מה יקרה אם תמחק אותה? ואיך היה אפשר לכתוב את זה אחרת?

ToCode
1 420
אז שוב יש לנו את ה computed המפורסם ולמרות ש Vue קצת יותר טוב מ MobX בלזהות מתי קוד רץ עדיין אפשר למצוא מקרי קצה (למשל כאן). אבל אני רוצה להתעכב כאן על רעיון אחר של Vue ששונה מ MobX, והוא הרקורסיביות של הריאקטיביות. ב Vue אם יש לי ביד אוביקט ריאקטיבי זה לא הופך את השדות שלו לריאקטיביים, וזה מייצר אינספור מצבים מעניינים. הקוד הבא מהחלק הרביעי של מדריך Vue שפרסמתי כאן ממחיש את הבעיה:

export default {
  props: ["id"],

  setup(props, context) {
    const { id } = toRefs(props);

    const { data, error, clear } = useSWR(
      () => `https://pokeapi.co/api/v2/pokemon/${id.value}`
    );

    watch(id, () => {
      clear({ broadcast: true });
    });

    return {
      data,
      error,
    };
  },
};
הקוד מושך קוד משרת מרוחק לפי מאפיין id שמתקבל בתור Property. כל הקוד הזה רץ בתוך computed, אבל העתקת שדה מהאוביקט props, שהוא בעצמו ריאקטיבי, מבטלת את הריאקטיביות של השדה. במילים אחרות הקוד עובד בזכות הפונקציה toRefs בשורה:
const { id } = toRefs(props);
שהופכת את כל השדות בתוך props לריאקטיביים בעצמם. אם בטעות נכתוב במקומה את השורה:
const { id } = props;
הקוד ייקח את id פעם אחת מ props אבל לא ימשיך לזהות שינויים בו, כי הוא מאבד את הריאקטיביות. ## סיכום - למה זה כל כך קשה אותו המכנה המשותף חוזר בכל הדוגמאות שראינו: הפער בין ציפיית המתכנתים לבין הפרטים הטכניים של הפריימוורק. מצד אחד פריימוורק ריאקטיבי אוטומטי מבטיח שכל עוד נעמוד בתנאים שלו הוא יצליח לזהות את כל השינויים ולבנות לבד ביטויים ריאקטיביים מהקוד שלנו, מצד שני התנאים הרבה יותר מסובכים ממה שהם מספרים בפירסומת וכשאנחנו עוברים מתוכניות דוגמה קטנות לעולם האמיתי אנחנו נתקלים בכל מקרי הקצה האלה, וקשה להבין מה בדיוק קורה ולמה דברים לא עובדים כמו בפירסומת. בעבודה עם פריימוורקים ריאקטיביים אנחנו כל הזמן צריכים להשאיר בראש את רשימת התנאים הריאקטיביים של הפריימוורק, ולהבין אם עשינו משהו ששובר את אחד החוזים. האם עשינו פעולה מחוץ לקוד שנמצא במעקב? האם העתקנו ערך במקום פרוקסי? או ששברנו את הריאקטיביות בדרך יצירתית אחרת. הצורך לשמור בראש כל הזמן את מערכת החוקים הנסתרת של הפריימוורק הוא שהופך את ה Debugging להרבה יותר מסובך.

ToCode
1 420
הפונקציה fetchProjects משנה את המשתנים this.githubProjects ו this.state, שהם משתנים ריאקטיביים. לכן את השינוי חייבים לכתוב בתוך פונקציית computed. בעוד שהפונקציה הראשית fetchProjects מזוהה בלי בעיה בצורה אוטומטית על ידי מובאקס ומקבלת את הריאקטיביות, שתי השורות האחרונות בבלוק ה try:
this.githubProjects = filteredProjects
this.state = "done"
כתובות בתוך בלוק אסינכרוני אחרי המילה await ולכן לא מזוהות בצורה אוטומטית. בלי העטיפה ב runInAction הקוד לא היה עובד. כשמגיעים לחבר את MobX לקוד ריאקט המילה computed הופכת לפונקציה בשם observer. רק קומפוננטות שמוגדרות עם הפונקציה הזאת יהיו ריאקטיביות ורק קוד שמוקף בפונקציה הזאת יתעדכן בזמן אמת. ביום רגיל מספיק להפעיל את הפונקציה observer על הקומפוננטה שמשתמשת במשתנים ריאקטיביים כדי שהקסם הריאקטיבי יעבוד. אבל בחיים האמיתיים אין ימים רגילים. שימו לב לקוד הבא מתוך שאלה שהופיעה ב Github Issues של הפרויקט:
// Login.js
export class Login extends Component {
    render() {
        return (
            <Formik
                initialValues={{
                    email: '',
                    password: ''
                }}
                onSubmit={this.handleSubmit}
                render={props => {
                    return (
                        <div>
                            <ActionButton onClick={props.submitForm} disabled={this.props.store.inProgress}>
                                    Save
                            </ActionButton>
                            <LoginForm {...props} />
                        </div>
                    )
                }}
            />
        );
    }

    handleSubmit(credentials) {
        this.props.store.login(credentials)
    }
}

export default inject('store')(observer(Login));
הקומפוננטה Login היא Observer ולכן כל התוכן שלה אמור להיות ריאקטיבי. אבל כשמסתכלים טוב רואים שהקומפוננטה יוצרת קומפוננטה אחרת בשם Formik ומעבירה לה חלק מהקוד באמצעות תבנית שנקראת Render Props. בגלל ש Formik בעצמה איננה observer, הריאקטיביות לא ממשיכה פנימה לתוך קוד ה JSX שעובר ל render ולכן הקומפוננטה לא מתעדכנת כשיש שינוי במאפיינים שהיא מסתכלת עליהם. אפילו אם הקומפוננטה Formik היתה Observer זה לא בטוח היה פותר את הבעיה כי אולי Formik לוקחת את הפונקציה הפנימית (שהעברתי לה בתוך render) ושולחת אותה לקומפוננטה אחרת. התיקון האמיתי הוא לזהות את התבנית הזאת ולהקיף את הקוד הפנימי ב Observer בעצמי - בדומה לעטיפה ב computed שראינו בדוגמה הקטנה. זה נראה כך:
export class Login extends Component {
    render() {
  const { inProgress } = this.props.store // this line causes re-render of ActionButton

        return (
            <Formik
                initialValues={{
                    email: '',
                    password: ''
                }}
                onSubmit={this.handleSubmit}
                render={props => {
                    return (
                        <Observer>
                        {() => (
                            <div>
                                <ActionButton onClick={props.submitForm} disabled={inProgress}>
                                        Save
                                </ActionButton>
                                <LoginForm {...props} />
                            </div>
                        )}
                    )
                }}
            />
        );
    }

    handleSubmit(credentials) {
        this.props.store.login(credentials)
    }
}

export default inject('store')(observer(Login));
בעוד שיחסית קל לראות את הדברים האלה כשקוראים את התיעוד או מדפדפים בין שאלות בגיטהאב, כשקוד אמיתי מפסיק להגיב כמו שצריך זה כבר הרבה יותר קשה לזהות את התבנית. ## ריאקטיביות ב Vue פריימוורק ריאקטיבי נוסף פופולרי הוא Vue שעובד בצורה מאוד דומה. הנה דוגמה לקוד ריאקטיבי ב Vue מתוך התיעוד שלהם:
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

count.value += 1;
console.log(plusOne.value) // 3

ToCode
1 420
# ריאקטיביות ו JavaScript הן שילוב מסוכן בשנים האחרונות אי אפשר להתעלם מהפופולריות של Frontend Frameworks ריאקטיביות. הרעיון הבסיסי הוא שבעוד שבתכנות "רגיל" כשאנחנו שמים תוצאה של ביטוי בתוך משתנה אז המשתנה מקבל את הערך החדש וזהו. אם בעתיד חלק מהאופרנדים בביטוי ישתנו זה לא ישפיע על אותו משתנה. בתכנות ריאקטיבי אנחנו שמים בתוך משתנה "ביטוי" וערכו של הביטוי מתעדכן אוטומטית כל פעם שהחלקים שלו מתעדכנים. כדי לראות את הקושי במימוש מנגנון ריאקטיבי ב JavaScript נתבונן בקטע הקוד הבא:
const input = document.querySelector('input');
const output = document.querySelector('output');

output.value = input.value;
זה קוד לא ריאקטיבי שלוקח ערך של שדה קלט input אחד וכותב אותו לשדה output. כל מה שכתוב ברגע ריצת הקוד בשדה הראשון מועתק לשדה השני, אבל אם התוכן ישתנה בעתיד אז לא יהיה לנו שינוי מקביל בתיבת הטקסט השניה. גירסה "ריאקטיבית" של הקוד היתה צריכה להמשיך ולעדכן את output כל פעם ש input מתעדכן. עכשיו אנחנו יכולים לכתוב קוד שיעשה את זה בעזרת אירועים:
const input = document.querySelector('input');
const output = document.querySelector('output');

input.addEventListener('input', () => {
    output.value = input.value;
});
אבל לא היינו קוראים לגירסה המפורשת עם האירועים "ריאקטיבית", כי היא מפורשת מדי ומתירנית מדי. בתוך קוד האירוע אפשר לכתוב כל דבר ולא רק לשנות את הערך של output. החלום של פריימוורקס ריאקטיביים ב JavaScript הוא לאפשר מעבר אוטומטי ושקוף מגירסת הביטוי לגירסת האירועים, בלי שנצטרך לבחור לאיזה אירוע להאזין ובלי שנצטרך למלא בעצמנו את קוד הטיפול באירוע. התיאוריה של פריימוורק ריאקטיבי פשוטה: אם אני כול להחליף את document.querySelector ולייצר אוביקט שלי שעוטף את ה DOM Element, אני אוכל לזהות מתי מישהו מנסה לקרוא מידע משדה value שלי, ולייצר אירוע שיפעיל את הקוד הזה מחדש כל פעם שערך השדה מתעדכן. בפועל הפריימוורקים שבנו מנגנון כזה מסתבכים כל אחד במקרי קצה אחרים. הנה שתי דוגמאות פשוטות מ Vue ו MobX ואחריהן סיכום עם המלצה. ## מובאקס, ריאקט ו Render Props מובאקס הוא פרייומורק ריאקטיבי אוטומטי לניהול סטייט. זה אומר שהוא יודע לעזור לנו לכתוב ביטויים שיתעדכנו בזמן אמת כשהחלקים שלהם מתעדכנים. הנה קצת קוד לדוגמה כדי שנבין על מה מדובר:
import { makeObservable, observable, computed, action } from "mobx";

export default class ObservableText {
  text = "";

  constructor(initialValue) {
    this.text = initialValue;

    makeObservable(this, {
      text: observable,
      reset: action
    });
  }

  reset() {
    this.text = "";
  }
}

const x = new ObservableText("hello");
const y = computed(() => x.text.toUpperCase());

console.log(x.text);
console.log(y.get());
x.text = "bye bye";
console.log(y.get());
הגדרתי מחלקה בשם ObservableText ויצרתי ממנה אוביקט בשם x. לאחר מכן הגדרתי ש y יהיה התוצאה של x.text.toUpperCase(). ארבעת השורות האחרונות הן המעניינות: אני מדפיס את x, מדפיס את הערך מתוך y, אחר כך משנה את x ומדפיס מחדש את הערך מתוך y. תוצאת ההדפסה:
hello 
HELLO 
BYE BYE 
ו-הנה הריאקטיביות שלנו. y הוגדר להיות תוצאה של ביטוי, וכשאחד החלקים של הביטוי מתעדכן באופן אוטומטי גם y חושב מחדש. צריך להגיד: זה לא שב y היתה שמורה הפונקציה והיא מופעלת מחדש כל פעם שאנחנו קוראים ל get. הפונקציה עצמה שהופכת את x לאותיות גדולות נקראת כל פעם ש x מתעדכן, ולא כל פעם שאנחנו קוראים את הערך מ y. זה הקסם הריאקטיבי. אז איפה פה הבעיה? הבעיות מתחילות במילה computed, או יותר מדויק בעובדה שאנחנו חייבים להגיד ל MobX כשאנחנו בונים ביטוי ריאקטיבי. הנה דוגמה מתוך התיעוד של MobX:
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            runInAction(() => {
                this.githubProjects = filteredProjects
                this.state = "done"
            })
        } catch (e) {
            runInAction(() => {
                this.state = "error"
            })
        }
    }

ToCode
1 420
2. אבל הפונקציה useSWR לא צריכה את כל ה props ולא תדע מה לעשות איתו. היא צריכה לקבל רק פונקציה שמחזירה URL. אם אני אשתמש באחד השדות מ props בתוך הפונקציה הזו, אז אותו שדה יהיה מספר פשוט ואי אפשר יהיה לזהות כשהוא משתנה. לכן יש ל Vue את הפונקציה toRefs. 3. הפונקציה toRefs, שאנחנו נשתמש בה בכל התחלה של פונקציית setup שצריכה לעבוד עם props, הופכת כל אחד מהשדות ב props למשתנה ריאקטיבי נפרד. הכתיב:
const { id } = toRefs(props);
לא מעתיק החוצה את השדה id, אלא עוטף את השדה id באוביקט ריאקטיבי כדי שכשנעביר אותו הלאה אפשר יהיה לזהות בו שינויים. וממש שתי שורות אחרי זה אני באמת מעביר את id הלאה, ובגלל שיצרתי ממנו אוביקט ריאקטיבי אני צריך להסתכל על שדה value שלו:
    const { data, error, clear } = useSWR(
      () => `https://pokeapi.co/api/v2/pokemon/${id.value}`
    );
דרך אגב - בגלל ש id הוא ריאקטיבי אני יכול להשתמש במנגנון של Vue שנקרא watch כדי לזהות כשהוא משתנה. הקטע הבא מאפס את data כל פעם שיש שינוי ב id:
    watch(id, () => {
      clear({ broadcast: true });
    });
הריאקטיביות של Vue זה משהו שצריך להתרגל אליו. ככל שנכתוב Concerns פשוטים ומנותקים כמו מונה הלחיצות או השעון אנחנו לא נצטרך להתעסק עם זה, אבל ברגע שמתחילים להגיע לדברים מעניינים ולהתממשקות עם מידע שמגיע מבחוץ כן תצטרכו לזהות איזה מידע צריך להיות "ריאקטיבי", כלומר כזה ששינויים בו צריכים להשפיע על מידע אחר, ולהגדיר אותו ככזה. במקרה של התקשורת אנחנו רואים שמזהה הפוקימון הוא ריאקטיבי כיוון שכשהוא משתנה גם שדה data צריך להתעדכן בהתאמה. מוזמנים לשחק עם הקוד המלא בקישור: https://codesandbox.io/s/nervous-pine-4hux3 או בתיבה: <iframe src="https://codesandbox.io/embed/nervous-pine-4hux3?fontsize=14&hidenavigation=1&theme=light" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="nervous-pine-4hux3" 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> ## עכשיו אתם 1. כתבו פונקציה בשם useWindowSize שמחזירה את גודל החלון הנוכחי. השתמשו בה מתוך setup של קומפוננטה והציגו בקומפוננטה את גודל החלון שקיבלתם. עדכנו את הקוד כך שהגודל ישתנה כל פעם שמשתמש משנה את גודל החלון. 2. כתבו פונקציה בשם useToggle שמוסיפה לקומפוננטה שלכם שדה בשם state ופונקציה בשם toggle. כל קריאה לפונקציה משנה את הערך של state מ"אמת" ל"שקר" ולהיפך. 3. כתבו פונקציה בשם useLocalStorage שמקבלת מפתח ומידע וכותבת את המידע ל Local Storage. אם המידע ישתנה לאורך חיי התוכנית יש לכתוב אותו מחדש ל Local Storage עם אותו מפתח.

ToCode
1 420
  const ticks = ref(0);

  const timer = setTimeout(function () {
    ticks.value += 1;
  }, 1000);

  onUnmounted(function () {
    clearTimeout(timer);
  });

  return {
    ticks
  };
}
עכשיו אני יכול להשתמש בו בתוך קומפוננטה כדי לקבל את ההתנהגות של "עושה משהו כל שניה", לדוגמה הקומפוננטה הבאה פשוט מציגה את השעון המתקתק:
<template>
  <div>
    <p>Ticks: {{ ticks }}</p>
  </div>
</template>

<script>
import timer from "../concerns/timer";

export default {
  setup() {
    return {
      ...timer(),
    };
  },
};
</script>
והקומפוננטה הבאה מציגה משפטים שמתחלפים כל שתי שניות:
<template>
  <div>
    <p>{{ currentText }}</p>
  </div>
</template>

<script>
import timer from "../concerns/timer";
const texts = [
  "All work and no play",
  "Make jack a dull boy",
  "The brown fox jumped",
  "Over the yellow dog",
];

export default {
  setup() {
    return {
      ...timer(),
    };
  },
  data() {
    return { x: 10 };
  },

  computed: {
    currentText() {
      return texts[Math.floor(this.ticks / 2) % texts.length];
    },
  },
};
</script>
אפשר לראות את שתיהן בקישור: https://codesandbox.io/s/brave-kepler-5l3h9 או בתיבה הבאה: <iframe src="https://codesandbox.io/embed/brave-kepler-5l3h9?fontsize=14&hidenavigation=1&theme=light" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="brave-kepler-5l3h9" 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> ## דוגמה: משיכת מידע משרת חיצוני מתוך קומפוננטה ספריות תוכנה רבות עושות שימוש בממשק ההרכבה, פשוט כי זה נוח. הספריה vswr מספקת פונקציית הרכבה שיודעת למשוך מידע משרת מרוחק, וגם לשמור אותו ב Cache מקומי בתוך הקומפוננטה כדי שפעם הבאה שנצטרך את המידע הזה נוכל לקבל אותו מהר יותר. אחרי התקנה נוכל לכתוב קומפוננטה כמו זו כדי להציג מידע על פוקימונים:
<template>
  <p>Fetching {{ id }}</p>

  <div v-if="isLoading">Loading ...</div>

  <div v-else-if="data">
    <p><b>ID: </b>{{ id }}</p>
    <p><b>Name: </b>{{ data.name }}</p>
    <p>
      <b>Abilities:</b
      >{{ data.abilities.map((a) => a.ability.name).join(", ") }}
    </p>
  </div>
  <div v-else-if="error">
    {{ error }}
  </div>
</template>

<script>
import { toRefs, watch } from "vue";
import { useSWR } from "vswr";

export default {
  props: ["id"],

  setup(props, context) {
    const { id } = toRefs(props);

    const { data, error, clear } = useSWR(
      () => `https://pokeapi.co/api/v2/pokemon/${id.value}`
    );

    watch(id, () => {
      clear({ broadcast: true });
    });

    return {
      data,
      error,
    };
  },
};
</script>
בדיוק כמו עם הפונקציות שאני כתבתי, גם useSWR מחזירה משתנים ריאקטיביים אותם אני שותל בתוך האוביקט ש setup מחזירה וממשיך להשתמש בהם מתוך התבנית או הפונקציות האחרות של הקומפוננטה. במקרה של useSWR אני מעביר לפונקציה נתיב ומקבל חזרה את המשתנים data, error ואת הפונקציה clear (ועוד כמה משתנים שתוכלו למצוא בתיעוד הספריה). הספריה תעדכן במועד מאוחר יותר את המשתנים כדי לשקף את המידע שהגיע מהשרת או השגיאה שהתקבלה. בואו נדבר על החיבור בין Props ל Setup, כי יש פה הרבה רעיונות חדשים. הפונקציה setup נקראת רק פעם אחת כשהקומפוננטה נוצרת, וזה אומר שהיא קוראת ל useSWR רק פעם אחת. אז איך ויו יודע להוציא בקשת רשת חדשה כש id משתנה? התשובה נקראת ריאקטיביות והיא היופי וגם הסירבול בקוד הזה: 1. המשתנה props עובר לפונקציה setup בתור פרמטר ראשון. משתנה זה הוא אוביקט ריאקטיבי, כלומר אפשר להעביר אותו הלאה לפונקציות נוספות והן יוכלו "להאזין" לשינויים בו.

ToCode
1 420
בגלל ש clicks הוא תוצאה של פונקציית ref, ובגלל שאני רוצה שהשינוי בערך שלו ישפיע על כל החלקים האחרים בקומפוננטה (או קומפוננטות) שמשתמשת בו, אני משתנה את שדה ה value שלו ולא את המשתנה עצמו. שינוי כזה מאפשר לחלקים אחרים במערכת ששומרים אצלם את clicks לקבל עדכון על השינוי ולעדכן את עצמם בהתאמה. הפונקציה setup מחזירה את השדה והפונקציה שמעדכנת אותו, ובקוד התבנית אני פשוט משתמש במשתנה ובפונקציה כאילו הם הוגדרו בבלוק data ו methods:
    <button @click="inc">{{ clicks }}</button>
מה הרווחנו בכל הסיפור, ואיך זה קשור לשימוש חוזר בקוד? אז הדבר החשוב לראות כאן הוא שאפשר להוציא את תוכן הפונקציה setup לפונקציות נפרדות, כל אחת אחראית על חלק אחר וקטן בפונקציונאליות של הקומפוננטה, וכך לבנות קומפוננטה בתור הרכבה (Composition) של התנהגויות. במקרה הקטן שלנו הייתי יכול לארגן את הקוד בצורה הבאה כדי לאפשר לשתי קומפוננטות לספור דברים. תחילה אני מוציא את הקוד המשותף לפונקציה ושומר אותה בקובץ נפרד. בפרויקט שלי יצרתי תיקיה בשם concerns ובתוכה קראתי לקובץ counter.js. זה התוכן שלו:
import { ref } from "vue";

export default function counter() {
  const clicks = ref(0);
  function inc() {
    clicks.value += 1;
  }

  return {
    clicks,
    inc
  };
}
את קוד הקומפוננטה ארגנתי מחדש לקוד הבא:

<template>
  <div>
    <label
      >Type some stuff:
      <input v-model="text" />
    </label>
    <p>You typed: {{ text }}</p>

    <button @click="inc">{{ clicks }}</button>
  </div>
</template>

<script>
import counter from "../concerns/counter";

export default {
  name: "HelloWorld",
  setup() {
    return {
      ...counter(),
    };
  },
  data() {
    return {
      text: "",
    };
  },
};
</script>
ועכשיו אני יכול ליצור קומפוננטה חדשה שתספור משהו אחר, למשל קומפוננטה שמציגה תיבת טקסט וסופרת כמה פעמים שינינו את הטקסט בתיבה:

<template>
  <div>
    <label
      >Type some stuff:
      <input v-model="text" @input="inc" />
    </label>
    <p>You typed: {{ text }}</p>
    <p>Text was changed {{ clicks }} times</p>
  </div>
</template>

<script>
import counter from "../concerns/counter";

export default {
  name: "CountChanges",
  setup() {
    return {
      ...counter(),
    };
  },
  data() {
    return {
      text: "",
    };
  },
};
</script>
מוזמנים לשחק עם קוד הפרויקט בקישור: https://codesandbox.io/s/focused-pasteur-i5864 או בתיבה הבאה: <iframe src="https://codesandbox.io/embed/focused-pasteur-i5864?fontsize=14&hidenavigation=1&theme=light" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="focused-pasteur-i5864" 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> ## דוגמה: תוספת מנגנון זמן לקומפוננטה עכשיו שאנחנו מבינים את המכניקה הבסיסית של קומפוזיציה בואו נראה איך לכתוב מספר מנגנונים מעניינים באמצעותה, ונתחיל במנגנון פעולה מבוססת זמן. כמו שהקומפוננטות הקודמות השתמשו ב clicks כדי "לספור" משהו, אני יכול להשתמש ב setTimeout כדי להעלות את המונה באחד אוטומטית כל שניה. בניגוד למונה הלחיצות, כשמדובר במנגנון זמן שמופעל על ידי setTimeout אני צריך גם לבטל את השעון אם משהו משתנה בעמוד והקומפוננטה שלי כבר לא מופיעה על המסך. לדוגמה אפשר לדמיין קרוסלת תמונות שמופיעה רק בתוך תפריט ניווט מסוים, או במצב עניינים מסוים של האפליקציה. כשמוציאים את קרוסלת התמונות מהמסך צריך לכבות את השעון כדי לא לתפוס סתם זיכרון. אוסף הפונקציות שמאפשרות לנו "לעשות דברים" כש"קורים דברים" ב Vue נקראות פונקציות מחזור חיים, ואפשר למצוא את הרשימה המלאה שלהן בקישור: https://v3.vuejs.org/guide/composition-api-lifecycle-hooks.html. נוסיף לפרויקט קובץ בשם concerns/timer.js עם התוכן הבא:
import { onUnmounted, ref } from "vue";

export default function timer() {

ToCode
1 420
# מדריך Vue למתחילים - חלק 4 - ממשק ההרכבות (Composition API) בחלקים הקודמים של המדריך למדנו איך לכתוב אפליקציית Vue מאפס, איך להעביר מידע בין קומפוננטות ואיך להוסיף מימד דינמי לתבניות שלנו. אם פספסתם אתם מוזמנים להעיף מבט חוזר: 1. חלק 1 - קומפוננטה ראשונה 2. חלק 2 - העברת מידע בין קומפוננטות 3. חלק 3 - תבניות דינמיות אם קראתם את שלושתם ופתרתם את התרגילים שם אתם כבר צריכים להרגיש מספיק בנוח עם כתיבת קומפוננטות כדי שנוכל להתחיל לדבר על שיתוף קוד בין קומפוננטות באמצעות Composition API. ## מהו שיתוף קוד בין קומפוננטות שיתוף קוד נשמע כמו אחד הרעיונות הראשונים שאנחנו רוצים ללמוד עליהם כשמגיעים ללמוד על פריימוורק חדש, ובכל זאת ב Front End Frameworks הוא נחשב לאתגר משמעותי. ברור, יש דברים שמאוד קל לשתף. אם ניקח שתי קומפוננטות שכל אחת מהן צריכה לבדוק אם מספר מסוים הוא ראשוני, אז נוכל בקלות לכתוב פונקציה שבודקת אם מספר מסוים הוא ראשוני ולהשתמש בה משתי הקומפוננטות. זה החלק הקל של שיתוף קוד. שיתוף קוד קל נוסף קורה במצב ששתי קומפוננטות צריכות להציג תוכן משותף או דומה. דוגמה שראינו באחד הפרקים הקודמים של המדריך היתה של תפריט ניווט שמציג מספר קישורי ניווט. לכל קישור ניווט יש מבנה מסוים והתנהגות מסוימת, והדרך להציג את אותו תוכן שוב ושוב היתה להוציא את התוכן החוצה לקומפוננטה נפרדת. כעת כל מי שירצה להשתמש באותו קישור ניווט יוכל לשלב את הקומפוננטה אצלו בקוד. שיתוף קוד הופך קשה כשאנחנו רוצים לשלב היבטים מסוימים של התנהגות ומידע בין קומפוננטות, בלי קשר לתבנית ובלי לצאת לקומפוננטה חדשה. קחו לדוגמה קרוסלת תמונות שצריכה כל כמה שניות להחליף תמונה. בנוסף דמיינו קומפוננטה של מבצעים מתחלפים שצריכה כל כמה שניות להחליף באנר של מבצע. שתי הקומפוננטות דומות במובן זה שיש להן התנהגות משותפת - לעשות משהו כל X זמן. אבל התבנית, המאפיינים, המתודות וכל שאר הדברים של הקומפוננטות יכולים להיות שונים לגמרי. שיתוף קוד באמצעות הרכבה נותן לנו פיתרון לאותם מצבים בהם יש מספר קומפוננטות עם התנהגות ומידע משותפים אבל עם תבנית שונה שלא מאפשרת לשלב אותן לקומפוננטה אחת. ## איך זה עובד בשביל להשתמש ב Composition API אנחנו צריכים להגדיר פונקציה אחת בשם setup. פונקציה זו מחזירה אוביקט עם כל המאפיינים שיצטרפו למאפיינים שמוחזרים מ data ובהם נוכל להשתמש בתבנית. אבל זה לא נגמר שם. הפונקציה יכולה להתחבר לאירועים שונים של הקומפוננטה ולהוסיף קוד שיקרה למשל כשהקומפוננטה מופיעה על המסך בפעם הראשונה או כשהקומפוננטה מתעדכנת. בדרך כלל בכתיב ה Composition אנחנו ניקח את הקוד המשותף למספר קומפוננטות, נוציא אותו לפונקציות נפרדות, ונקרא להן מפונקציית ה setup של כל אחת מהקומפוננטות. קוד זה ישנה את הקומפוננטה וירכיב עליה את הפונקציונאליות החדשה. נתבונן בקומפוננטה הבאה:
<template>
  <div>
    <label
      >Type some stuff:
      <input v-model="text" />
    </label>
    <p>You typed: {{ text }}</p>

    <button @click="inc">{{ clicks }}</button>
  </div>
</template>

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

export default {
  name: "HelloWorld",
  setup() {
    const clicks = ref(0);
    function inc() {
      clicks.value += 1;
    }

    return {
      clicks,
      inc,
    };
  },
  data() {
    return {
      text: "",
    };
  },
};
</script>
תבנית הקומפוננטה כוללת שני חלקים שמתאימים לשני המנגנונים בקוד הקומפוננטה: החלק הראשון משתמש בכלים עליהם למדנו בפרקים הקודמים של המדריך. הוא מציג תיבת קלט, כל טקסט שנכתב בתיבה מעדכן את המשתנה text בתוך אוביקט ה data של הקומפוננטה ומשתנה זה גם מוצג על המסך מתחת לתיבת הקלט. החלק היותר מעניין הוא החלק השני, שמציג כפתור שסופר כמה פעמים לחצו עליו. הקוד שקשור לכפתור פועל בגישה מאוד שונה. בתוך קוד הקומפוננטה הוספתי פונקציה בשם setup, ובתוכה הגדרתי משתנה בשם clicks להיות תוצאה של הפונקציה ref. ב Vue הפונקציה ref מחזירה מצביע למשתנה שיכול לשנות את ערכו, וכל שינוי שלו אוטומטית ישפיע על כל חלק אחר במערכת שמשתמש בערך. הגדרתי גם פונקציה בשם inc שמשנה את הערך של clicks, אבל בצורה לא שגרתית:
    const clicks = ref(0);
    function inc() {
      clicks.value += 1;
    }

ToCode
1 420
# טיפ זום: קונטקסט ישבתי השבוע עם הבן שלי להקשיב לזום על בניית סקין למיינקרפט. מיינקרפט מסתבר זה משחק שילדים אוהבים וסקין זה פשוט ציור של דמות שמכינים לבד. בזום היו באזור ה 75 ילדים, כולם מאוד מתרגשים ומאוד Engaged, משהו שמבוגר שמעביר זומים יכול רק לחלום עליו. לא עברו שלוש דקות של שיחה לפני שהתחילו השאלות: 1. את תראי גם איך להעלות את הסקין לאקסבוקס? 2. איך מפעילים מיינקרפט? 3. איך משחקים מיינקרפט? 4. את תראי איך למצוא סקינים באינטרנט? 5. זה עובד על Windows 10? ועוד עשרות שאלות באותו סגנון, שאף אחת מהן לא רלוונטית למה שאותה מרצה רצתה להראות, ואף תשובה לא קידמה אף אחד מהילדים לכיוון הידע שהוא רצה לקבל. בשלב מסוים היה צריך להשתיק את כל המשתתפים כדי להתקדם, וגם זה לא בדיוק עזר. הטעות בזום ברורה אבל מספיק נפוצה כדי שיהיה שווה לכתוב עליה והיא נקראת קונטקסט. קונטקסט מסוים היה כי כל השאלות באמת הלכו סביב משחק מיינקרפט, אבל זה כנראה לא הספיק. כשאין קונטקסט אנחנו מתפזרים, בזום כמו בחיים האמיתיים. קונטקסט אומר שכולם בחדר צריכים להיות מסונכרנים עם משימה ספציפית שצריך לבצע, יודעים בדיוק למה נכנסנו ואיך אנחנו אמורים לצאת. במקרה של סקין למיינקרפט המשחק היה נראה אחרת לגמרי אם בכניסה היינו מקבלים לינק לתוכנת יצירת הסקינים, עדיף תוכנה שעובדת אונליין ללא צורך בהתקנה, והסבר קצר שאומר שעד סוף המפגש כל משתתף יצייר סקין ויפרסם אותו בתערוכה שאנחנו מכינים. בום, כל השאלות נמחקו. עכשיו יש לנו משימה וכל השאלות החדשות יהיו על המשימה. אם יש לכם Engagement ו Context, כל מפגש שתעבירו, בזום או בחיים האמיתיים, יהיה זמן שהושקע כמו שצריך.

ToCode - Статистика та аналітика Telegram каналу @tocodeil