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

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

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

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

כדי להקיף סגנונות, צריך ליצור שורש של הצללה.

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

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

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

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

למה? צאצאים שנוצרו על ידי הרכיב שלך הם חלק מההטמעה שלו, ועליהם להיות פרטי. ללא ההגנה של שורש הצללית, מחוץ ל-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. אלא אם יש תרחיש לדוגמה שאפשר לפתור רק באמצעות התכונה הזו, להימנע ממנה.

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

למה? מפתח שמשתמש ברכיב שלך יכול לנסות להגדיר מאפיין על האלמנט לפני שהגדרתו נטענה. במיוחד אם משתמשים ב-framework שמטפל ברכיבים של טעינה וחותמים אותם. לדף, ולקשר את המאפיינים שלהם למודל.
דוגמה רכיב <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 של תיבת הסימון. אם ההגדרה של הוראות הסימון המאפיין המסומן לפני שהרכיב שודרג.

<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;
    ...
  }
}