Elementy niestandardowe umożliwiają tworzenie własnych tagów HTML. Ta lista kontrolna zawiera sprawdzone metody tworzenia elementów wysokiej jakości.
Elementy niestandardowe umożliwiają rozszerzanie kodu HTML i definiowanie własnych tagów. Są to niezwykle przydatne funkcje, ale są też niskopoziomowe, co oznacza, że nie zawsze jest jasne, jak najlepiej wdrożyć własny element.
Aby ułatwić Ci korzystanie z tej usługi, przygotowaliśmy listę kontrolną. Obejmuje wszystkie elementy,
które według nas są potrzebne, by utworzyć element niestandardowy.
Lista kontrolna
model DOM
Utwórz pierwiastek cienia, aby uwzględnić style. |
Why? |
Kodowanie stylów w głównym katalogu cienia elementu zapewnia, że będą one działać niezależnie od tego, gdzie są używane. Jest to szczególnie ważne, gdy programista chce umieścić element w grzebu głównym cienia innego elementu. Dotyczy to nawet prostych elementów, takich jak pole wyboru czy przycisk opcji. Może się tak zdarzyć, że jedyną treścią w źródle cienia będą same style.
|
Przykład |
Element
<howto-checkbox> .
|
Utwórz pierwiastek cienia w konstruktorze.
|
Why? |
Konstruktor polega na tym, że masz wyłączną wiedzę o danym elemencie.
To dobry moment na skonfigurowanie szczegółów implementacji, aby uniknąć pomyłki nad innymi elementami. Wykonanie tej czynności w późniejszym wywołaniu zwrotnym, na przykład przy użyciu connectedCallback , oznacza, że trzeba zabezpieczyć się przed sytuacją, w której element zostanie odłączony, a następnie ponownie dołączony do dokumentu.
|
Przykład |
Element
<howto-checkbox> .
|
Umieść wszystkie elementy podrzędne tworzone przez ten element w korzeniu cienia.
|
Why? |
Elementy podrzędne utworzone przez Twój element są częścią jego implementacji i powinny być prywatne. Bez ochrony cienia głównego kod JavaScript może nieumyślnie zakłócić działanie tych elementów.
|
Przykład |
Element
<howto-tabs> .
|
Użyj elementu <slot>, aby wyświetlić elementy podrzędne Light DOM w modelu Shadow DOM.
|
Why? |
Zezwalaj użytkownikom komponentu na określanie treści, ponieważ elementy podrzędne HTML zwiększają jego kompozycję. Jeśli przeglądarka nie obsługuje elementów niestandardowych, zagnieżdżone treści pozostają dostępne, widoczne i dostępne.
|
Przykład |
Element
<howto-tabs> .
|
Ustaw styl wyświetlania :host (np. block , inline-block , flex ), chyba że wolisz używać domyślnego stylu inline .
|
Why? |
Domyślnie elementy niestandardowe mają wartość display: inline , więc ustawienie ich width lub height nie będzie miało żadnego efektu. Często jest to zaskoczeniem dla deweloperów i może powodować problemy z układem strony. Jeśli nie chcesz korzystać z ekranu inline , zawsze ustawiaj domyślną wartość display .
|
Przykład |
Element
<howto-checkbox> .
|
Dodaj styl wyświetlania :host z uwzględnieniem ukrytego atrybutu.
|
Why? |
Element niestandardowy z domyślnym stylem display , np. :host { display: block } , zastąpi
atrybut hidden o niższej szczegółowości.
Może to być zaskoczeniem, jeśli oczekujesz ustawienia atrybutu hidden w elemencie tak, aby renderował go display: none . Oprócz domyślnego stylu display dodaj obsługę stylu hidden z użyciem atrybutu :host([hidden]) { display: none } .
|
Przykład |
Element
<howto-checkbox> .
|
Atrybuty i właściwości
Nie zastępuj atrybutów globalnych ustawionych przez autora.
|
Why? |
Atrybuty globalne to te, które występują we wszystkich elementach HTML. Oto niektóre przykłady: tabindex i role . W przypadku elementu niestandardowego można ustawić początkową wartość tabindex na 0, aby można było go zaznaczyć za pomocą klawiatury. Zawsze jednak najpierw sprawdź, czy programista korzystający z Twojego elementu nie ustawił tej wartości na inną. Jeśli na przykład atrybut tabindex ma wartość -1, jest to sygnał, że nie chce, aby ten element był interaktywny.
|
Przykład |
Element
<howto-checkbox> . Szczegółowo wyjaśniamy to w sekcji Nie zastępuj autora strony
|
Zawsze akceptuj dane podstawowe (ciągi, liczby, wartości logiczne) jako atrybuty lub właściwości.
|
Why? |
Elementy niestandardowe, takie jak ich wbudowane odpowiedniki, powinny być konfigurowane.
Konfigurację można przekazywać deklaratywnie, za pomocą atrybutów lub imperatywnie za pomocą właściwości JavaScriptu. W idealnej sytuacji każdy atrybut powinien być również połączony z odpowiednią usługą.
|
Przykład |
Element
<howto-checkbox> .
|
Staraj się synchronizować podstawowe atrybuty danych i ich właściwości, odzwierciedlając dane w każdej usłudze i na odwrót.
|
Why? |
Nigdy nie wiadomo, jak użytkownik wejdzie w interakcję z Twoim elementem. Może ustawić właściwość w JavaScript, a następnie odczytać tę wartość za pomocą interfejsu API takiego jak getAttribute() . Jeśli każdy atrybut ma odpowiadającą jej właściwość i oba odzwierciedlają je wszystkie, ułatwi to użytkownikom pracę z Twoim elementem. Innymi słowy, wywołanie setAttribute('foo', value) powinno też spowodować ustawienie odpowiadającej mu właściwości foo i odwrotnie. Oczywiście są wyjątki od tej reguły. Nie powinny odnosić się do właściwości o wysokiej częstotliwości, np. currentTime w odtwarzaczu. Kieruj się własną oceną Jeśli wygląda na to, że użytkownik wejdzie w interakcję z właściwością lub atrybutem, a odzwierciedlenie tego problemu nie jest męczące, zrób to.
|
Przykład |
Element
<howto-checkbox> . Zostało to szczegółowo wyjaśnione w artykule Unikanie problemów z ponownym korzystaniem z usługi.
|
Staraj się akceptować jako właściwości tylko dane rozszerzone (obiekty, tablice).
|
Why? |
Ogólnie rzecz biorąc, nie ma przykładów wbudowanych elementów HTML, które za pomocą atrybutów akceptują dane sformatowane (zwykłe obiekty i tablice JavaScript). Dane rozszerzone są akceptowane przez wywołania metod lub właściwości. Przyjmowanie danych rozszerzonych jako atrybutów ma kilka oczywistych wad: zserializowanie dużego obiektu w ciągu znaków może być kosztowne, a w tym procesie ciągnięcia zostaną utracone wszystkie odniesienia do obiektów. Jeśli na przykład utworzysz ciąg znaków dla obiektu, który odwołuje się do innego obiektu lub węzła DOM, te odwołania zostaną utracone.
|
Nie odzwierciedlają właściwości rozszerzonych danych w atrybutach.
|
Why? |
Odzwierciedlenie właściwości rozszerzonych w atrybutach jest niepotrzebnie kosztowne oraz wymaga serializacji i deserializacji tych samych obiektów JavaScript. Jeśli nie masz przypadku użycia, który można rozwiązać tylko za pomocą tej funkcji, prawdopodobnie lepiej tego unikać.
|
Rozważ sprawdzenie, czy istnieją właściwości, które mogły zostać ustawione przed uaktualnieniem elementu.
|
Why? |
Programista korzystający z Twojego elementu może próbować ustawić jego właściwość, zanim zostanie wczytana jego definicja. Jest to szczególnie istotne, jeśli deweloper używa platformy, która obsługuje wczytywanie komponentów, oznaczanie ich na stronie i powiązanie ich właściwości z modelem.
|
Przykład |
Element
<howto-checkbox> . Wyjaśnienie zostało szczegółowo wyjaśnione w sekcji Ustawianie właściwości jako leniwej.
|
Nie zgłaszaj zajęć samodzielnie.
|
Why? |
Elementy, które muszą określać stan, powinny używać atrybutów. Uznaje się, że atrybut class należy do dewelopera, który korzysta z Twojego elementu, więc pisanie w nim samodzielnie może spowodować nieumyślne użycie klas programisty.
|
Wydarzenia
Wysyłaj zdarzenia w odpowiedzi na wewnętrzne działanie komponentu.
|
Why? |
Właściwości komponentu mogą się zmieniać w zależności od działania, o którym wie tylko on. Może to być na przykład zakończenie wczytywania licznika czasu lub animacji albo zakończenie ładowania zasobu. Warto wysyłać zdarzenia w odpowiedzi na te zmiany, aby powiadomić hosta o innym stanie komponentu.
|
Nie wysyłaj zdarzeń w odpowiedzi na ustawienie usługi hosta (przepływ danych w dół).
|
Why? |
Wysyłając zdarzenie w odpowiedzi na ustawienie hosta dla usługi, jest niepotrzebne (host wie bieżący stan, ponieważ po prostu go ustawił). Wysyłanie zdarzeń w odpowiedzi na ustawienie hosta w usłudze może spowodować zapętlenie w systemach wiązania danych.
|
Przykład |
Element
<howto-checkbox> .
|
Filmy objaśniające
Nie zastępuj autora strony
Może się zdarzyć, że programista korzystający z Twojego elementu będzie chciał zastąpić część jego początkowego stanu. Może to być na przykład zmiana role
ARIA lub możliwość koncentracji za pomocą tabindex
. Zanim zastosujesz własne wartości, sprawdź, czy te atrybuty globalne zostały ustawione.
connectedCallback() {
if (!this.hasAttribute('role'))
this.setAttribute('role', 'checkbox');
if (!this.hasAttribute('tabindex'))
this.setAttribute('tabindex', 0);
Ustaw właściwości jako leniwe
Programista może próbować ustawić właściwość w elemencie, zanim zostanie wczytana jego definicję. Jest tak szczególnie wtedy, gdy deweloper używa struktury, która obsługuje wczytywanie komponentów, wstawianie ich na stronie i powiązanie ich właściwości z modelem.
W poniższym przykładzie Angular deklaratywnie wiąże właściwość isChecked
swojego modelu z właściwością checked
pola wyboru. Jeśli definicja pola wyboru instrukcji to leniwe ładowanie, możliwe, że Angular może spróbować ustawić zaznaczoną właściwość, zanim element zostanie uaktualniony.
<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>
Element niestandardowy powinien rozwiązać ten problem, sprawdzając, czy w jego instancji ustawiono już jakieś właściwości. <howto-checkbox>
demonstruje ten wzorzec za pomocą metody _upgradeProperty()
.
connectedCallback() {
...
this._upgradeProperty('checked');
}
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
_upgradeProperty()
przechwytuje wartość z nieuaktualnionej instancji i usuwa właściwość, aby nie zasłaniała obiektu ustawiającego właściwości elementu niestandardowego.
Dzięki temu po załadowaniu definicji elementu może on od razu odzwierciedlić prawidłowy stan.
Unikanie problemów związanych z ponownym przejściem
Użycie parametru attributeChangedCallback()
do odzwierciedlenia stanu właściwości podstawowej, np.:
// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'checked')
this.checked = newValue;
}
Może to jednak utworzyć nieskończoną pętlę, jeśli metoda ustawiająca również odzwierciedla atrybut.
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');
}
Możesz też zezwolić funkcji ustawiającej na podstawie atrybutu na odzwierciedlenie tego atrybutu i zezwolić na ustalenie jego wartości na podstawie atrybutu.
set checked(value) {
const isChecked = Boolean(value);
if (isChecked)
this.setAttribute('checked', '');
else
this.removeAttribute('checked');
}
get checked() {
return this.hasAttribute('checked');
}
W tym przykładzie dodanie lub usunięcie atrybutu spowoduje też ustawienie właściwości.
Poza tym attributeChangedCallback()
może służyć do obsługi efektów ubocznych, takich jak stosowanie stanów 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;
...
}
}