קורס Front End למתכנתים שיעור שיתוף פעולה בין קומפוננטות


זהו נושא דיון מלווה לערך המקורי שב־https://www.tocode.co.il/bundles/frontend/lessons/components-events

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

הי

בכל שיעור יש שני טאבים: “וידאו” ו"טקסט". כשאני מדבר על טקסט לצד הסרטון אני מתכוון לטאב “טקסט” (מעל נגן הוידאו יש את הכפתורים לעבור טאבים)

תודה רבה ועוד שאלה. בקובץ MAIN.JS שהוספת את הקומפוננטות לHTML בשורה 3 ו8 עשית הוספה של האלמנט ולא של האובייקט שיצרת
עשית appendchild(el) ולא appenchild(counter) , למה בעצם זאת הדרך אני לא מבין.

ה DOM מייצג את האלמנטים שרואים על המסך. אפשר להוסיף אליו רק דברים שהם מסוג DOM Elements (כמו div, p, button, input, img וכו). ה counter שלי הוא מסוג מיוחד מהמחלקה שאני יצרתי ואינו DOM Element

לייק 1

היי ינון, אני מתקשה להבין את הצורך בeventName
וגם את השורה הזאת -

if (!this.listeners[eventName]) {
      this.listeners[eventName] = [];
    }

הי אביב,

המטרה שלנו היא לבנות מערכת לדיווח על אירועים בה קומפוננטות מסוימות יכולות לדווח ש"קרה משהו" וקומפוננטות אחרות יכולות להגיד שאם “קרה X” אז צריך “לעשות Y”

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

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

button.addEventListener('click', handleClick);

שם האירוע click הוא חשוב כי יש לכפתור כל מיני אירועים; ומנגנון האירועים הוא חשוב כי הכפתור לא מכיר אותי ואני לא מכיר אותו.

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

bus.on('click', function() { console.log('Ouch!'); });

וחלק אחר בכלל לא קשור במערכת יוכל להגיד:

bus.trigger('click')

וזה יגרום להפעלה של הפונקציה.

אחרי הבניה אפשר לחזור להסתכל על הוידאו ואני בטוח שהוא יהיה יותר ברור

היי ינון אני לא יודע למה אבל אני לא מצליח להבין את זה עדין… אני אמשיך עם שאר השיעורים ואני אחזור לזה בהמשך

אחלה לפעמים זאת גם שיטה שעוזרת

אם תראה שאתה חוזר לזה ולא מסתדר ותרצה להמשיך לחפור על זה יכול לשלוח לי פה גם הודעה פרטית או אימייל ונמשיך בפרטי

ינון שלום
במחלקה Counter יש את הפונקציה שמאזינה לארוע לחיצה על כפתור

this.button.addEventListener(‘click’, this.inc.bind(this));
הפנקציה כבר נמצאת בתוך מחלקה. מה ההיתרון לגרסה עם ה bind מול הגרסה הזו
this.button.addEventListener(‘click’, this.inc);
שהיא פשוטה יותר לדעתי

הי,
אני מדבר על זה בשיעור כאן:
https://www.tocode.co.il/bundles/frontend/lessons/this-and-events

בדיוק השיעור שראיתי כדי להיזכר לפני לפני ששאלתי את השאלה.
הפנק inc היא פנקצית חץ לכן לפי הבנתי ה bind מיותר.

הי וסליחה על הדיליי בתשובה היו קצת צרות טכניות עם המחשב -

אני רואה עכשיו למה אתה מתכוון! כן באמת יש שם כפילות אפשר לוותר על ה bind וזה עדיין יעבוד

לייק 1

היי ינון,

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

אני מנסה להבין מה המטרה של פונקציית SUBSCRIBE, אומנם הסברת את המטרה שלה שהיא אמורה להאזין לאירועים אבל ישבתי ושברתי תראש להבין את הקוד והגעתי למצב שהקוד הבא עובד אותו הדבר כאשר מחקתי את פונקציית SUBSCRIBE, וככה לא יווצר אובייקט שבתוכו מפתח שמכיל מערך ובתוך המערך יש איבר אחד שמכיל את פונקציית UPDATE. ועדיין קורה מה שצריך לקרות, כשלוחצים על הכפתור זה קורא לפונקציית EMIT שהיא בסופו של דבר קוראת לפונקציית UPDATE ואז היא בודקת אם לעדכן או לא לעדכן את VAL.
לא יודע אם אני נכנס למקומות מיותרים אבל אשמח לתגובך.

תודה רבה.

class EventBus {
  constructor() {
      //this.handlers = {};
  }

  /*subscribe(eventName, handler) {
      if (!this.handlers.eventName) {
          this.handlers.eventName = [];
      }
      this.handlers.eventName.push(handler);
  }*/

  emit(eventName, ...info) {
      /*const handlers = (this.handlers.eventName || []);
      for (let handler of handlers) {
          handler(...info);
      }*/
      panel.update(...info);
  }
}

class Display {
  constructor(el) {
      this.setupUi(el);
      //bus.subscribe('value', this.update);
  }

  update = (newValue) => {
      const currentValue = this.panel.textContent;
      if (newValue > currentValue) {
          this.panel.textContent = newValue;
      }
  }
   
  setupUi(el) {
      el.innerHTML = `
          <p>And the maximum value is: <span class="maxvalue"></span></p>
      `;
      this.panel = el.querySelector('.maxvalue');
  }
}

class Counter {
  constructor(el, bus) {
    this.val = '';
    this.bus = bus;
    this.setupUi(el);
  }

  setupUi(el) {
    el.innerHTML = `
            <p>
            <button>Click To Inc</button>
            <span class="log"></span>
            </p>
        `;
    this.log = el.querySelector('.log');
    this.button = el.querySelector('button');
    this.button.addEventListener('click', this.inc.bind(this));
    this.updateUi();
  }

  inc() {
    this.val++;
    this.bus.emit('value', this.val);
    this.updateUi();
  }

  updateUi() {
    this.log.textContent = this.val;
  }
}

const bus = new EventBus();

const panelElement = document.createElement('div');
document.body.appendChild(panelElement);
const panel = new Display(panelElement);

for (let i=0; i < 5; i++) {
  const el = document.createElement('div');
  document.body.appendChild(el);
  const counter = new Counter(el, bus);
}

המשכתי לשחק עוד קצת ואפילו הורדתי את העברת BUS לקומפוננטות.

הי גיל,

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

לגבי הקוד אני רואה שחוץ מהקוד שמחקת יש גם שורה שהוספת לפונקציה emit:

panel.update(...info)

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

// file Counter.js
  inc = () => {
    this.val++;
    panel.update(this.val);
    this.updateUi();
  }

ואז באמת אנחנו לא צריכים את כל הסיפור של Event-ים ו bus.

וככה אנחנו מגיעים חזרה לשאלה - למה בעצם כן צריך את זה? למה לא לתקשר ישירות בין קומפוננטות כל הזמן?
ועוד שאלת המשך - למה צריך ״להעביר״ את bus לקומפוננטות, כשאנחנו רואים בקוד שכתבת שאפשר פשוט לפנות לדברים לפי השם שלהם, למשל ב Counter אנחנו ניגשים ל panel ישירות לפי השם שלו, בלי שאף אחד ״העביר״ אותו בבנאי.

לפני שאני עונה אשמח לשמוע מה דעתך - איזה בעיות יכולות להיווצר בגישה הזאת? איזה שינויים בקוד יהיה קשה לבצע?

היי ינון, תודה על התגובה המהירה ותודה על הפרגון :slight_smile:

אני לא ממש מבין עדיין מה הכוונה כשאתה אומר לתקשר ישירות בין קומפוננטות ולההפך - לא לתקשר בין קומפוננטות.
הכל מעורפל לי כרגע. אני מניח שצריך את המשתנה this בשביל שיעביר מידע מדבר לדבר? אני לא יודע להסביר את זה עדיין כי הכל מעורפל לי כרגע.
אני יודע שבכללי כשיש פונקציה בנאית שמכילה thisים, אז בזכות this המידע שבפונקציה עובר לאובייקט new.
וככה זה עם קלאס/קומפוננטה גם מן הסתם. אבל כנראה שעדיין לא מתיישב לי הנושא כשמגיעים לחיבור קומפוננטות ומנהל אירועים.

בקוד של השיעור בעיקר לא הבנתי למה פונקציה subscribe/on יוצרת אובייקט ולתוכו מפתח עם ערך שהוא מערך ובתוך המערך הפונקציה update. ואז קוראים לפונקציה update שבתוך המערך דרך פונקציה emit בלופ על המערך כדי לגשת לפונקציה. (לקח לי הרבה זמן להבין את מה שכתבתי כאן ברמה הטכנית, עכשיו נשאר ברמת ההיגיון וההבנה הכללית חחח). לא באמת עניתי לך על השאלות אבל זה מה שאני יודע כרגע.

נכון בתוך מחלקה באמת ה this מאפשר לנו ״לחבר״ בין הפונקציות השונות של אותה מחלקה. לשמור מידע שבעצם יהיה שם כשנצטרך אותו יותר מאוחר

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

אז השאלה - למה לא לכתוב ככה? למה להתאמץ עם רעיונות יותר מסובכים כשאפשר לפתור את הבעיה ממש בקלות?

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

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

היי ינון,

לצערי הרב המשימה שהבאת לי קשה מידי עבורי ואני לא מצליח להבין איך אני יוצר את הקומפוננטה של הערך הקטן ביותר.

מצרף את הקוד למרות שלא באמת עשיתי משהו שם.

class EventBus {
  constructor() {
      //this.handlers = {};
  }

  /*subscribe(eventName, handler) {
      if (!this.handlers.eventName) {
          this.handlers.eventName = [];
      }
      this.handlers.eventName.push(handler);
  }*/

  emit(eventName, ...info) {
      /*const handlers = (this.handlers.eventName || []);
      for (let handler of handlers) {
          handler(...info);
      }*/
      panel.update(...info);
      lowValuePanel.updateLow(...info);
  }
}

class Display {
  constructor(el) {
      this.val = 0;
      this.setupUi(el);
      this.updateUi();
      //bus.subscribe('value', this.update);
  }

  update = (newValue) => {
      const currentValue = this.panel.textContent;
      if (newValue > currentValue) {
          this.panel.textContent = newValue;
      }
  }
   
  setupUi(el) {
      el.innerHTML = `
          <p>And the maximum value is: <span class="maxvalue"></span></p>
      `;
      this.panel = el.querySelector('.maxvalue');
  }

  updateUi() {
    this.panel.textContent = this.val;
  }
}

class DisplayLow {
  constructor(el) {
    this.val= 0;
    this.setupUi(el);
    this.updateUi();
  }

  setupUi(el) {
    el.innerHTML = `<p>And the minimum value is: <span class="minvalue"></span></p>`;
    this.lowValue = el.querySelector('.minvalue');
  }

  updateLow = (newValue) => {
    if (newValue > this.lowValue.textContent) {
      this.lowValue.textContent = newValue;
    }
  }

  updateUi() {
    this.lowValue.textContent = this.val;
  }
}

class Counter {
  constructor(el, bus) {
    this.val = 0;
    this.bus = bus;
    this.setupUi(el);
  }

  setupUi(el) {
    el.innerHTML = `
            <p>
            <button>Click To Inc</button>
            <span class="log"></span>
            </p>
        `;
    this.log = el.querySelector('.log');
    this.button = el.querySelector('button');
    this.button.addEventListener('click', this.inc.bind(this));
    this.updateUi();
  }

  inc() {
    this.val++;
    this.bus.emit('value', this.val);
    this.updateUi();
  }

  updateUi() {
    this.log.textContent = this.val;
  }
}

const bus = new EventBus();


const panelElement = document.createElement('div');
document.body.appendChild(panelElement);
const panel = new Display(panelElement);


const lowValueDiv = document.createElement('div');
document.body.appendChild(lowValueDiv);
const lowValuePanel = new DisplayLow(lowValueDiv);


for (let i=0; i < 5; i++) {
  const el = document.createElement('div');
  document.body.appendChild(el);
  const counter = new Counter(el, bus);
}

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

אני חושב שהכיוון של ״הפוך מ Display״ הוא הכי טוב, כלומר כמו שכתבת אם newValue יותר קטן ממה שכבר יש לי אז אני אדרוס את מה שכבר יש לי עם newValue, אבל לפי הקוד נראה שפשוט טעית בסימן:


  updateLow = (newValue) => {
    if (newValue > this.lowValue.textContent) {
      this.lowValue.textContent = newValue;
    }
  }

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

ניסיתי את זה וזה אף פעם לא יעבוד כי אף פעם לא יהיה מצב ש-newValue (מצב התחלתי 1) יהיה קטן יותר מ-0 שזה המצב ההתחלתי.

updateLow = (newValue) => {
    if (newValue < this.lowValue.textContent) {
      this.lowValue.textContent = newValue;
    }
  }

יו נכון לא חשבתי על זה.

אולי נוסיף לקומפוננטה של ה Counter כפתור נוסף שיוריד ב-1 את הערך? משהו כזה:

class Counter {
  constructor(el, bus) {
    this.val = 0;
    this.bus = bus;
    this.setupUi(el);
  }

  setupUi(el) {
    el.innerHTML = `
            <p>
            <button class="plus">+1</button>
            <button class="minus">-1</button>
            <span class="log"></span>
            </p>
        `;
    this.log = el.querySelector('.log');
    this.plusButton = el.querySelector('.plus');
    this.plusButton.addEventListener('click', this.inc.bind(this, 1));

    this.minusButton = el.querySelector('.minus');
    this.minusButton.addEventListener('click', this.inc.bind(this, -1));

    this.updateUi();
  }

  update(by=1) {
    this.val = this.val + by;
    this.bus.emit('value', this.val);
    this.updateUi();
  }

  updateUi() {
    this.log.textContent = this.val;
  }
}