שיטות מומלצות לשימוש ברכיבים מותאמים אישית

רכיבים מותאמים אישית מאפשרים לכם ליצור תגי HTML משלכם. רשימת המשימות הזו מפרטת שיטות מומלצות שיעזרו לכם ליצור רכיבים איכותיים.

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

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

רשימת המשימות

DOM של צל

יוצרים שורש צל כדי לכלול סגנונות.

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

יוצרים את בסיס הצללית ב-constructor.

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

מציבים את הצאצאים שהרכיב יוצר בשורש הצל שלו.

למה? ילדים שנוצרו על ידי הרכיב הם חלק מההטמעה שלו, וצריכים להיות פרטיים. ללא הגנה על Root מסוג 'צללים', JavaScript חיצוני עשוי להפריע לילדים האלה בלי כוונה.
דוגמה הרכיב <howto-tabs>.

שימוש ב-<משבצת> כדי להקרין צאצאי DOM בהירים ל-DOM של הצל

למה? עליך לאפשר למשתמשים של הרכיב לציין תוכן ברכיב שלך, בתור צאצאים של HTML, כדי להפוך את הרכיב שלך לאוטומטי. גם כשדפדפן לא תומך ברכיבים מותאמים אישית, התוכן המקונן נשאר זמין, גלוי ונגיש.
דוגמה הרכיב <howto-tabs>.

יש להגדיר סגנון תצוגה של :host (לדוגמה, block, inline-block, flex) אלא אם יש העדפה לברירת המחדל של inline.

למה? כברירת מחדל, רכיבים מותאמים אישית הם display: inline, ולכן להגדרה של width או height לא תהיה השפעה. בדרך כלל הדפוס הזה מפתיע את המפתחים, והוא עלול לגרום לבעיות שקשורות לפריסת הדף. אלא אם יש העדפה לתצוגה של inline, תמיד צריך להגדיר ערך ברירת מחדל מסוג display.
דוגמה הרכיב <howto-checkbox>.

צריך להוסיף סגנון תצוגה :host שמכבד את המאפיין המוסתר.

למה? רכיב מותאם אישית עם סגנון ברירת המחדל display, למשל :host { display: block }, יבטל את רמת הספציפיות הנמוכה יותר המאפיין hidden. זה עשוי להפתיע אותך אם ציפית להגדיר את המאפיין hidden ברכיב שלך כדי שיעבד אותו display: none. בנוסף לסגנון ברירת המחדל של display, יש להוסיף תמיכה ב-hidden עם :host([hidden]) { display: none }.
דוגמה הרכיב <howto-checkbox>.

מאפיינים ונכסים

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

למה? מאפיינים גלובליים הם מאפיינים שקיימים בכל רכיבי ה-HTML. לדוגמה: tabindex ו-role. ייתכן שתרצו להגדיר את הערך הראשוני של tabindex ברכיב מותאם אישית כ-0, כדי שיהיה אפשר להתמקד בו. אבל תמיד כדאי לבדוק קודם אם המפתח שמשתמש ברכיב שלך הגדיר ערך אחר. לדוגמה, אם הם הגדירו את הערך tabindex כ- -1, זה סימן שהם לא רוצים שהרכיב יהיה אינטראקטיבי.
דוגמה הרכיב <howto-checkbox>. אפשר לקרוא הסבר נוסף בנושא אין לשנות את מחבר הדף.

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

למה? צריך להגדיר רכיבים מותאמים אישית, כמו הרכיבים המובנים המובְנים שלהם. ניתן להעביר את ההגדרה באמצעות הצהרה, דרך מאפיינים או באופן יזום באמצעות מאפייני JavaScript. באופן אידיאלי, צריך גם לקשר כל מאפיין לנכס מתאים.
דוגמה הרכיב <howto-checkbox>.

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

למה? אף פעם אי אפשר לדעת איך המשתמש יקיים אינטראקציה עם הרכיב. הם עשויים להגדיר מאפיין ב-JavaScript, ואז לצפות לקרוא את הערך באמצעות ממשק API כמו getAttribute(). אם לכל מאפיין יש נכס מתאים ושניהם ישקפו, למשתמשים יהיה קל יותר לעבוד עם הרכיב. במילים אחרות, קריאה ל-setAttribute('foo', value) אמורה להגדיר גם נכס foo תואם, ולהפך. כמובן שיש חריגים לכלל הזה. אסור לשקף בנגן וידאו מאפיינים של תדר גבוה, למשל currentTime. צריך להפעיל שיקול דעת. אם נראה שמשתמש יתקיים אינטראקציה עם נכס או מאפיין, ולא קשה לשקף זאת, כדאי לעשות זאת.
דוגמה הרכיב <howto-checkbox>. במאמר מניעת בעיות של חזרה על מילים מוסבר בפירוט על כך.

השאיפה היא לקבל רק נתונים עשירים (אובייקטים, מערכים) כמאפיינים.

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

אל תשקף מאפיינים עשירים של נתונים.

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

כדאי לבדוק אם יש מאפיינים שהוגדרו לפני שדרוג הרכיב.

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

אין ליישם את הכיתות באופן עצמאי.

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

אירועים

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

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

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

למה? שליחת אירוע בתגובה להגדרת נכס על ידי מארח היא מיותרת (המארח יודע מה המצב הנוכחי כי הוא רק הגדיר אותו). שליחת אירועים בתגובה להגדרת נכס של מארח עשויה לגרום ללולאות אינסופיות עם מערכות קישור נתונים.
דוגמה הרכיב <howto-checkbox>.

הסברים

אין לשנות את מחבר הדף

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

connectedCallback() {
  if (!this.hasAttribute('role'))
    this.setAttribute('role', 'checkbox');
  if (!this.hasAttribute('tabindex'))
    this.setAttribute('tabindex', 0);

הפיכת נכסים לעצלנים

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

בדוגמה הבאה, Angular מקשר באופן מוצהר את המאפיין isChecked של המודל שלו לנכס checked של תיבת הסימון. אם ההגדרה של תיבת הסימון של מסלול API נטענה באופן מדורגת, יכול להיות ש-Angular ינסה להגדיר את המאפיין המסומן לפני השדרוג של הרכיב.

<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>

רכיב מותאם אישית צריך לטפל בתרחיש הזה על ידי בדיקה אם יש כבר מאפיינים שמוגדרים במכונה שלו. התווית <howto-checkbox> ממחישה את הדפוס הזה באמצעות שיטה שנקראת _upgradeProperty().

connectedCallback() {
  ...
  this._upgradeProperty('checked');
}

_upgradeProperty(prop) {
  if (this.hasOwnProperty(prop)) {
    let value = this[prop];
    delete this[prop];
    this[prop] = value;
  }
}

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

מניעת בעיות של חזרה חוזרת

מפתה להשתמש ב-attributeChangedCallback() כדי לשקף את המצב בנכס בסיס, לדוגמה:

// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'checked')
    this.checked = newValue;
}

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

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    // OOPS! This will cause an infinite loop because it triggers the
    // attributeChangedCallback() which then sets this property again.
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

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

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

get checked() {
  return this.hasAttribute('checked');
}

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

לבסוף, אפשר להשתמש ב-attributeChangedCallback() כדי לטפל בתופעות לוואי כמו החלת מצבי ARIA.

attributeChangedCallback(name, oldValue, newValue) {
  const hasValue = newValue !== null;
  switch (name) {
    case 'checked':
      // Note the attributeChangedCallback is only handling the *side effects*
      // of setting the attribute.
      this.setAttribute('aria-checked', hasValue);
      break;
    ...
  }
}