מדריך Vue למתחילים - חלק 7 - ניהול מידע גלובאלי

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

פוסטים קודמים בסידרה:

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

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

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

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

  5. פרק 5 - דוגמת פיתוח נגן YouTube ב Vue

  6. פרק 6 - יישומי Single Page Apps עם Vue Router

בפרק זה נדבר על מידע גלובאלי ואיך אנחנו משתמשים בו ביישומי Vue.

1. מהו מידע גלובאלי

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

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

<CompA>
    <CompB>
        <CompC>
            <CompD>
                <CompE />
            </CompD>
        </CompC>
    </CompB>
    <CompF />
</CompA>

אז אם גם קומפוננטה CompF וגם קומפוננטה CompE צריכות מידע מסוים, אז בשיטה הקלאסית של שמירת המידע בקומפוננטה העליונה נצטרך לשמור את המידע ב data של CompA, להעביר אותו בתור Property מ A ל B, מ B ל C, מ C ל D ומ D ל E. רק דמיינו מה יקרה אם נרצה בעתיד לשנות את שם השדה.

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

הטריק הוא שתי הפונקציות ref ו reactive. הפונקציה ref מקבלת משתנה בודד (מחרוזת או מספר) והופכת אותו ל Reference. ב Vue, אוביקט מסוג Reference הוא משהו שיש לו שדה value, וכל פעם שמשנים את שדה value שלו כל הקומפוננטות שהשתמשו בשדה ה value יתעדכנו אוטומטית. לכן אם יש לי משתנה בודד שצריך להישמר מחוץ לעץ הקומפוננטות ולהשפיע על מספר קומפוננטות, אני אשמור אותו בתור אוביקט ref, כל הקומפוננטות ישתמשו בו רגיל באמצעות import, וכשאחת הקומפוננטות תעדכן את תוכן המשתנה כל האחרות יתעדכנו בצורה אוטומטית לשקף את הערך החדש.

הפונקציה reactive היא דומה אבל מבצעת את הפעולה בצורה רקורסיבית. היא נועדה לטפל באוביקטים או מערכים - והיא הופכת את כל השדות של האוביקט או המערך בצורה רקורסיבית לאוביקטי ref.

נראה איך להשתמש בפונקציות באמצעות שתי דוגמאות:

2. דוגמה 1: שיתוף Counter

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

בשביל זה אני יוצר פרויקט Vue חדש ובתוכו תיקיה חדשה בשם global. בתוכה אני יוצר קובץ בשם counter.js עם התוכן הבא:

import { ref } from 'vue';

export const counter = ref(0);

נסו להוסיף את counter ל window ולגשת אליו מה console. אתם תראו שבשביל להגיע למספר 0 ששמור בתוכו תצטרכו להדפיס את counter.value.

נמשיך לכתוב שתי קומפוננטות שישתמשו במשתנה זה. הקומפוננטה CompA מציגה את הערך:

<template>
  <p>The counter value is: {{counter}}</p>
</template>

<script>
import { counter } from '../global/counter';

export default {
  data() {
    return { counter };
  }
};
</script>

וזה מעניין! כי במקום לכתוב בטמפליייט counter.value אני כותב פשוט counter. מסתבר ש Vue מספיק חכם בשביל לזהות אוביקטי Reference שמופיעים בטמפלייט ובצורה אוטומטית לגשת ל value שלהם.

במקום אחר ביישום אני יכול לכתוב קומפוננטה חדשה בשם CompB שתשנה את ערך המונה:

<template>
  <p>I don't know what counter is, but I can increase it !</p>
  <button @click="inc()">Click to increase</button>
</template>

<script>
import { counter } from '../global/counter';

export default {
  methods: {
    inc() {
      counter.value++;
    }
  }
};
</script>

עכשיו אם תשימו את שתי הקומפוננטות האלה על המסך תוכלו לראות שלחיצה על הכפתור בקומפוננטה CompB מעדכנת את הערך על המסך שמופיע בקומפוננטה CompA.

3. דוגמה 2: שיתוף לוח משבצות

נמשיך לדוגמה יותר מעניינת הפעם עם אוביקט. כתבו בתוך תיקיית global קובץ בשם paint.js עם התוכן הבא:

import { reactive } from 'vue';

export const data = reactive({
  currentColor: 1,
  colors: ['white', 'red', 'blue', 'yellow', 'green', 'pink', 'orange', 'black'],
  grid: [
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0],
    [0, 5, 5, 5, 5, 5, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0]
  ]
});

export function paint(row, column) {
  data.grid[row][column] = data.currentColor;
}

export function selectColor(colorIndex) {
  data.currentColor = colorIndex;
}

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

הקומפוננטה ImageView יכולה להציג ולערוך את הציור:

<template>
  <div>
    <h1>The Image</h1>
    <div v-for="(row, rowIndex) in grid" class="row" :key="rowIndex">
      <div
          v-for="(colorCode, columnIndex) in row"
          class="column"
          :key="columnIndex"
          :style="{backgroundColor: colors[colorCode]}"
          @click="() => paint(rowIndex, columnIndex)"
          >
      </div>
    </div>
  </div>
</template>

<script>
import { data, paint } from '../global/paint';

export default {
  name: 'ImageView',
  data() {
    return {
      grid: data.grid,
      colors: data.colors,
    };
  },
  methods: {
    paint,
  },
}
</script>

<style>
  .row {
    display: block;
    padding: 0;
    margin: 0;
  }

  .column {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 1px solid #c9c9c9;
    padding: 0;
    margin: 0;
    vertical-align: top;
  }
</style>

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

<template>
  <div id="app">
    <ImageView />
    <ImageView />
  </div>
</template>

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

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

<template>
  <div>
    <h1>Color Picker</h1>
    <div
        class="color"
        v-for="(clr, index) in colors"
        :style="{ backgroundColor: clr }"
        @click="() => selectColor(index)"
        :key="index"
        >
    </div>
  </div>
</template>

<script>
import { data, selectColor } from '../global/paint';

export default {
  setup() {
    return {
      selectColor,
      colors: data.colors,
    }
  }
};
</script>

<style>
  .color {
    width: 50px;
    height: 20px;
    display: inline-block;
    padding: 0;
  }
</style>

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

4. תרגילים

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

  2. הוסיפו טיימר כך שבאופן אוטומטי כל משבצת משנה את הצבע כל כמה שניות לצבע אחד אחורה יותר, כלומר משבצת שהיתה צבועה בצבע 5 תהפוך אחרי מספר שניות לצבע 4, ואז אחרי כמה שניות לבצע 3 וכך הלאה עד שתגיע חזרה לצבע 0 הלבן. חישבו: איפה כדאי לכתוב את הטיימר הזה - בתור קומפוננטה נפרדת או בתוך הלוגיקה הגלובאלית?

  3. הוסיפו תמיכה ב paint.js במברשות: מברשת מציירת יותר ממשבצת אחת, לדוגמה מברשת של עיגול תצייר את כל העיגול מסביב לנקודה שלחצו עליה. הוסיפו קומפוננטה לבחירת מברשת נוכחית.

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