แนวทางปฏิบัติแนะนำสำหรับองค์ประกอบที่กำหนดเอง

องค์ประกอบที่กำหนดเองให้คุณสร้างแท็ก HTML ของคุณเองได้ รายการตรวจสอบนี้ครอบคลุมแนวทางปฏิบัติแนะนำที่จะช่วยคุณสร้างองค์ประกอบที่มีคุณภาพสูง

องค์ประกอบที่กำหนดเองช่วยให้คุณสามารถขยาย HTML และกำหนดแท็กของคุณเอง ฟีเจอร์เหล่านี้เป็นฟีเจอร์ที่มีประสิทธิภาพอย่างเหลือเชื่อ แต่ก็อยู่ในระดับต่ำเช่นกัน ซึ่งหมายความว่าไม่มีความชัดเจนว่าจะใช้องค์ประกอบของตัวเองอย่างไรจึงจะดีที่สุดเสมอไป

เราได้จัดทำรายการตรวจสอบนี้ขึ้นมาเพื่อให้คุณสร้างประสบการณ์การใช้งานที่ดีที่สุด โดยจะแจกแจงทุกสิ่งที่เราคิดว่าจำเป็นต้องใช้ในการ เป็นองค์ประกอบที่กำหนดเองที่มีลักษณะการทำงานที่ดี

เช็กลิสต์

Shadow DOM

สร้างเงามืดเพื่อห่อหุ้มสไตล์

เพราะเหตุใด การห่อหุ้มรูปแบบใน Shadow รูทขององค์ประกอบจะช่วยให้มั่นใจได้ว่ารูปแบบจะทำงานได้ไม่ว่าจะใช้งานที่ใด การดำเนินการนี้สำคัญอย่างยิ่งหากนักพัฒนาซอฟต์แวร์ต้องการวางองค์ประกอบของคุณไว้ในรากเงาขององค์ประกอบอื่น ซึ่งมีผลกับองค์ประกอบง่ายๆ เช่น ช่องทำเครื่องหมายหรือปุ่มตัวเลือก ในบางกรณี อาจมีเฉพาะเนื้อหาที่อยู่ในรากเงาของเงาเองเท่านั้น
ตัวอย่าง องค์ประกอบ <howto-checkbox>

สร้างเงามืดในตัวสร้าง

เพราะเหตุใด เครื่องมือสร้างคือเมื่อคุณมีความรู้เฉพาะตัวเกี่ยวกับองค์ประกอบ ตอนนี้เป็นเวลาที่เหมาะแก่การตั้งค่ารายละเอียดการใช้งานซึ่งคุณไม่ต้องการให้องค์ประกอบอื่นๆ เลอะเทอะ การดำเนินการนี้ในโค้ดเรียกกลับในภายหลัง เช่น connectedCallback หมายความว่าคุณจะต้องป้องกันกรณีที่องค์ประกอบถูกถอดออกและแนบเข้ากับเอกสารอีกครั้ง
ตัวอย่าง องค์ประกอบ <howto-checkbox>

วางองค์ประกอบย่อยที่องค์ประกอบสร้างขึ้นลงในรากที่แสงเงา

เพราะเหตุใด องค์ประกอบย่อยที่สร้างโดยองค์ประกอบของคุณเป็นส่วนหนึ่งของการใช้งานและควรเป็นแบบส่วนตัว หากไม่มีการปกป้องรูทที่แสงเงา JavaScript ภายนอกอาจรบกวนการทำงานของเด็กเหล่านี้โดยไม่ตั้งใจ
ตัวอย่าง องค์ประกอบ <howto-tabs>

ใช้ <slot> เพื่อฉายภาพ Light DOM ไปยัง Shadow DOM

เพราะเหตุใด อนุญาตให้ผู้ใช้คอมโพเนนต์ระบุเนื้อหาในคอมโพเนนต์เป็น HTML ย่อยเพื่อให้เขียนคอมโพเนนต์ได้ง่ายขึ้น เมื่อเบราว์เซอร์ไม่รองรับองค์ประกอบที่กำหนดเอง เนื้อหาที่ซ้อนอยู่จะยังคงอยู่ มองเห็น และเข้าถึงได้
ตัวอย่าง องค์ประกอบ <howto-tabs>

ตั้งค่ารูปแบบการแสดงผล :host (เช่น block, inline-block, flex) เว้นแต่ว่าคุณต้องการใช้ค่าเริ่มต้นเป็น inline

เพราะเหตุใด องค์ประกอบที่กำหนดเองจะมีค่าเป็น display: inline โดยค่าเริ่มต้น ดังนั้นการตั้งค่า width หรือ height จะไม่มีผล เรื่องนี้มักเป็นเรื่องน่าประหลาดใจสำหรับนักพัฒนาซอฟต์แวร์และอาจทำให้เกิดปัญหาเกี่ยวกับการจัดเลย์เอาต์หน้าเว็บ คุณควรกำหนดค่า display เริ่มต้นเสมอหากไม่ต้องการใช้จอแสดงผล inline
ตัวอย่าง องค์ประกอบ <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() หากทุกแอตทริบิวต์มีพร็อพเพอร์ตี้ที่สัมพันธ์กันและแสดงทั้ง 2 แอตทริบิวต์ จะทำให้ผู้ใช้ทำงานร่วมกับองค์ประกอบได้ง่ายขึ้น กล่าวคือ การเรียกใช้ setAttribute('foo', value) ควรตั้งค่าพร็อพเพอร์ตี้ foo ที่เกี่ยวข้องด้วย และในทางกลับกันด้วย แน่นอนว่ามีข้อยกเว้นสำหรับกฎนี้ คุณไม่ควรแสดงพร็อพเพอร์ตี้ความถี่สูง เช่น currentTime ในโปรแกรมเล่นวิดีโอ โปรดใช้วิจารณญาณที่ดีที่สุด หากดูเหมือนว่าผู้ใช้จะโต้ตอบกับพร็อพเพอร์ตี้หรือแอตทริบิวต์ และตัวแปรก็ไม่ยุ่งยากในการแสดงให้เห็นถึงการโต้ตอบดังกล่าว
ตัวอย่าง องค์ประกอบ <howto-checkbox> ซึ่งดูเพิ่มเติมได้ในหัวข้อหลีกเลี่ยงปัญหาการกลับมาใช้ซ้ำ

มุ่งเน้นการรับเฉพาะข้อมูลแบบสมบูรณ์ (วัตถุ อาร์เรย์) เป็นคุณสมบัติ

เพราะเหตุใด กล่าวโดยทั่วไปคือไม่มีตัวอย่างขององค์ประกอบ HTML ในตัวที่ยอมรับ Rich Data (ออบเจ็กต์และอาร์เรย์ JavaScript แบบธรรมดา) ผ่านแอตทริบิวต์ ระบบจะยอมรับข้อมูลแบบสมบูรณ์ผ่านการเรียกใช้เมธอดหรือพร็อพเพอร์ตี้แทน มีข้อเสียที่ชัดเจน 2 ประการในการยอมรับข้อมูลแบบสมบูรณ์เป็นแอตทริบิวต์ กล่าวคือ การทำให้ออบเจ็กต์ขนาดใหญ่เป็นต่อเนื่องเป็นสตริงอาจมีค่าใช้จ่ายสูง และการอ้างอิงออบเจ็กต์จะสูญหายไปในกระบวนการสตริงนี้ ตัวอย่างเช่น หากคุณสตริงออบเจ็กต์ที่มีการอ้างอิงไปยังออบเจ็กต์อื่นหรือโหนด DOM การอ้างอิงเหล่านั้นจะหายไป

ไม่แสดงพร็อพเพอร์ตี้ข้อมูลแบบสมบูรณ์ในแอตทริบิวต์

เพราะเหตุใด การแสดงพร็อพเพอร์ตี้ข้อมูลสื่อสมบูรณ์กับแอตทริบิวต์มีค่าใช้จ่ายสูงโดยไม่จำเป็น ซึ่งทำให้ออบเจ็กต์ JavaScript เดียวกันเป็นอนุกรมและดีซีเรียลไลซ์ หากไม่มีกรณีการใช้งานที่สามารถแก้ไขได้ด้วยฟีเจอร์นี้เท่านั้น เราขอแนะนำให้หลีกเลี่ยง

ลองตรวจสอบพร็อพเพอร์ตี้ที่อาจตั้งค่าไว้ก่อนการอัปเกรดองค์ประกอบ

เพราะเหตุใด นักพัฒนาซอฟต์แวร์ที่ใช้องค์ประกอบของคุณอาจพยายามตั้งค่าพร็อพเพอร์ตี้ในองค์ประกอบก่อนที่จะโหลดคำจำกัดความ โดยเฉพาะอย่างยิ่งหากนักพัฒนาซอฟต์แวร์ใช้เฟรมเวิร์กที่จัดการคอมโพเนนต์การโหลด ประทับกับหน้าเว็บ และเชื่อมโยงพร็อพเพอร์ตี้กับโมเดล
ตัวอย่าง องค์ประกอบ <howto-checkbox> อธิบายเพิ่มเติมในหัวข้อทำให้พร็อพเพอร์ตี้เป็นแบบ Lazy Loading

โปรดอย่าสมัครชั้นเรียนด้วยตนเอง

เพราะเหตุใด องค์ประกอบที่ต้องแสดงสถานะไว้ด้วยแอตทริบิวต์ โดยทั่วไปแอตทริบิวต์ class จะถือว่าเป็นของนักพัฒนาซอฟต์แวร์ที่ใช้องค์ประกอบของคุณ และการเขียนแอตทริบิวต์ไปยังแอตทริบิวต์ดังกล่าวด้วยตนเองอาจเหยียบชั้นเรียนนักพัฒนาซอฟต์แวร์โดยไม่ได้ตั้งใจ

กิจกรรม

ส่งเหตุการณ์เพื่อตอบสนองต่อกิจกรรมของคอมโพเนนต์ภายใน

เพราะเหตุใด คอมโพเนนต์อาจมีพร็อพเพอร์ตี้ที่เปลี่ยนแปลงไปตามกิจกรรมที่คอมโพเนนต์ของคุณเท่านั้นที่ทราบ เช่น เมื่อตัวจับเวลาหรือภาพเคลื่อนไหวเล่นจนจบ หรือโหลดทรัพยากรเสร็จ การส่งเหตุการณ์เพื่อตอบสนองต่อการเปลี่ยนแปลงเหล่านี้จะช่วยแจ้งให้โฮสต์ทราบว่าสถานะของคอมโพเนนต์นั้นแตกต่างออกไป

อย่าส่งเหตุการณ์เพื่อตอบสนองต่อการตั้งค่าพร็อพเพอร์ตี้ของโฮสต์ (โฟลว์ข้อมูลขาลง)

เพราะเหตุใด การส่งเหตุการณ์เพื่อตอบสนองต่อการตั้งค่าพร็อพเพอร์ตี้ของโฮสต์นั้นไม่จำเป็น (โฮสต์จะทราบสถานะปัจจุบันเพราะเพิ่งตั้งค่า) การส่งเหตุการณ์เพื่อตอบสนองต่อการตั้งค่าพร็อพเพอร์ตี้ของโฮสต์อาจทำให้เกิดการวนซ้ำแบบไม่สิ้นสุดกับระบบการเชื่อมโยงข้อมูล
ตัวอย่าง องค์ประกอบ <howto-checkbox>

วิดีโออธิบาย

อย่าลบล้างผู้เขียนหน้าเว็บ

เป็นไปได้ว่านักพัฒนาซอฟต์แวร์ที่ใช้องค์ประกอบของคุณอาจต้องการลบล้างสถานะเริ่มต้นบางสถานะ ตัวอย่างเช่น การเปลี่ยน ARIA role หรือความสามารถในการโฟกัสด้วย tabindex ตรวจสอบว่ามีการตั้งค่าเหล่านี้และแอตทริบิวต์ร่วมอื่นๆ หรือไม่ ก่อนที่จะใช้ค่าของคุณเอง

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

ทำให้ที่พักเป็นแบบ Lazy Loading

นักพัฒนาซอฟต์แวร์อาจพยายามตั้งค่าพร็อพเพอร์ตี้ในองค์ประกอบของคุณก่อนที่จะมีการโหลดคำจำกัดความ โดยเฉพาะอย่างยิ่งหากนักพัฒนาซอฟต์แวร์ใช้เฟรมเวิร์กที่จัดการคอมโพเนนต์การโหลด การแทรกคอมโพเนนต์ลงในหน้าเว็บ และเชื่อมโยงพร็อพเพอร์ตี้กับโมเดล

ในตัวอย่างต่อไปนี้ Angular จะเชื่อมโยงพร็อพเพอร์ตี้ isChecked ของโมเดลกับพร็อพเพอร์ตี้ checked ของช่องทำเครื่องหมายอย่างชัดเจน หากการกำหนดช่องทำเครื่องหมายวิธีการโหลดแบบ Lazy Loading ก็เป็นไปได้ว่า 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');
}

อีกวิธีหนึ่งคือการอนุญาตให้ตัวตั้งค่าพร็อพเพอร์ตี้แสดงถึงแอตทริบิวต์ และให้ Getter กำหนดค่าตามแอตทริบิวต์

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