自訂元素可讓您建構自己的 HTML 標記。這份檢查清單涵蓋最佳做法,可協助您建立優質元素。
自訂元素可讓您擴充 HTML 並定義自己的標記。這是非常強大的功能,但也屬於低階,因此不一定能清楚實作自有元素的最佳方式。
為協助您打造最佳體驗,我們整理了這份檢查清單。可細分所有我們認為達到自訂元素所需的一切元素。
檢查清單
陰影 DOM
建立用來封裝樣式的陰影根。 |
為什麼? |
將元素陰影根層級的樣式封裝可確保無論在何處都能運作。如果開發人員想將元素置於其他元素的陰影根內部,這點就特別重要。這適用於核取方塊或圓形按鈕等簡單的元素。陰影根層級的唯一內容會是樣式本身。 |
範例 |
<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 不會產生任何作用。這通常是對開發人員造成驚訝的現象,而且可能會造成網頁版面配置相關問題。除非您偏好使用 inline 螢幕,否則應一律設定預設的 display 值。 |
範例 |
<howto-checkbox> 元素。 |
新增遵循隱藏屬性的 :host 顯示樣式。
|
為什麼? |
使用預設 display 樣式的自訂元素 (例如 :host { display: block } ) 會覆寫內建明確度的內建
hidden 屬性。如果您預期在元素上設定 hidden 屬性來顯示 display: none ,可能會感到意外。除了預設的 display 樣式之外,您還可以使用 :host([hidden]) { display: none } 新增對 hidden 的支援。 |
範例 |
<howto-checkbox> 元素。 |
屬性和屬性
不要覆寫作者設定和全域屬性。
|
為什麼? |
全域屬性是指必須出現在所有 HTML 元素中的屬性。一些範例包括 tabindex 和 role 。建議將自訂元素初始的 tabindex 設為 0,讓自訂元素成為可聚焦的鍵盤。不過,請務必先檢查,確認使用您元素的開發人員是否已將此元素設為其他值。舉例來說,如果將 tabindex 設為 -1,代表他們不想讓元素進行互動。 |
範例 |
<howto-checkbox> 元素。詳情請參閱不要覆寫網頁作者。
|
一律接受原始資料 (字串、數字、布林值) 做為屬性或屬性。
|
為什麼? |
自訂元素 (例如內建項目) 應可供設定。
設定可透過宣告方式、屬性或透過 JavaScript 屬性以命令方式傳遞。在理想情況下,所有屬性都應連結至對應的資源。 |
範例 |
<howto-checkbox> 元素。 |
盡量讓原始資料屬性和屬性保持同步,同時反映資源與屬性,反之亦然。
|
為什麼? |
您永遠不知道使用者將如何與您的元素互動。他們可能會在 JavaScript 中設定屬性,然後預期使用 getAttribute() 等 API 讀取該值。如果每項屬性都有對應的屬性,且兩者都反映了相同的屬性,則可讓使用者更容易使用您的元素。換句話說,呼叫 setAttribute('foo', value) 也應一併設定對應的 foo 屬性,反之亦然。當然,這項規則仍有例外情況。請勿反映高頻率屬性,例如影片播放器中的 currentTime 。請運用最佳判斷。如果是使用者與屬性或屬性互動,並不容易反映該屬性或屬性, |
範例 |
<howto-checkbox> 元素。詳情請參閱避免重複發生的問題一節。 |
僅接受多媒體資料 (物件、陣列) 做為屬性。
|
為什麼? |
一般來說,沒有任何範例內建 HTML 元素透過屬性 (純 JavaScript 物件和陣列) 接受多媒體資料。透過方法呼叫或屬性接受多媒體資料。接受多媒體資料做為屬性有以下兩個明顯缺點:將大型物件序列化為字串可能非常昂貴,而且所有物件參照在這個字串化過程中都會遺失。舉例來說,如果您將具有參照其他物件 (或可能是 DOM 節點) 的物件字串化,這些參照就會遺失。 |
不要反映屬性的多媒體資料屬性。
|
為什麼? |
將多媒體資料屬性反映到屬性成本非常高昂,而且必須對相同的 JavaScript 物件進行序列化和還原序列化作業。除非您有隻能透過這項功能解決的用途,否則建議您避免使用。 |
建議檢查元素在升級之前是否設定過屬性。
|
為什麼? |
載入元素定義之前,使用您元素的開發人員可能會嘗試設定元素的屬性。這種情況在開發人員使用的架構上會處理載入元件、在頁面中標示元件,以及將元件屬性繫結至模型時更是如此。 |
範例 |
<howto-checkbox> 元素。詳情請參閱將屬性設為延遲。 |
請勿自行套用類別。
|
為什麼? |
需要表示狀態的元素應使用屬性來達到此目的。一般而言,使用您的元素是 class 屬性的擁有者,因此可能會在無意間寫入開發人員類別。 |
活動
因應內部元件活動分派事件。
|
為什麼? |
例如,當計時器或動畫完成,或是資源載入完畢時,元件可能會因為元件知道的活動而發生變更的屬性。建議您根據這些變更進行分派事件,通知主機元件的狀態不同。 |
請勿因主機設定屬性 (向下資料流) 而分派事件。
|
為什麼? |
因回應主機設定而分派事件的做法非常多 (主機知道目前狀態,因為已設定該屬性)。因應主機設定屬性分派事件,可能會導致資料繫結系統產生無限迴圈。 |
範例 |
<howto-checkbox> 元素。 |
釋疑影片
不要覆寫網頁作者
使用元素的開發人員可能會想覆寫部分初始狀態。例如,變更其 ARIA role
或 tabindex
的可聚焦性。請先確認這些屬性和任何其他全域屬性是否都已設定,再套用您自己的值。
connectedCallback() {
if (!this.hasAttribute('role'))
this.setAttribute('role', 'checkbox');
if (!this.hasAttribute('tabindex'))
this.setAttribute('tabindex', 0);
將屬性設為延遲
載入定義之前,開發人員可能會嘗試在元素上設定屬性。尤其在開發人員使用的架構來處理載入元件、將元件插入頁面,以及將屬性繫結至模型時,這一點尤其重要。
在以下範例中,Angular 透過宣告將模型的 isChecked
屬性繫結至核取方塊的 checked
屬性。如果如何勾選方塊的定義延遲載入,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()
會擷取未升級執行個體的值並刪除該屬性,因此不會遮蔽自訂元素本身的屬性 setter。這樣一來,在元素定義最終載入時,就能立即反映正確的狀態。
避免重複發生的問題
建議您使用 attributeChangedCallback()
來反映基礎屬性的狀態,例如:
// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'checked')
this.checked = newValue;
}
但如果屬性 setter 也反映了屬性,這可能會建立無限迴圈。
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');
}
另一個方法是讓屬性 setter 反映屬性,並讓 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;
...
}
}