Best practice per elementi personalizzati

Gli elementi personalizzati ti consentono di creare tag HTML personalizzati. Questo elenco di controllo illustra le best practice per aiutarti a creare elementi di alta qualità.

Gli elementi personalizzati ti consentono di estendere l'HTML e definire i tuoi tag. È un funzione incredibilmente potente, ma sono anche di basso livello, il che significa che sempre chiaro il modo migliore per implementare il proprio elemento.

Per aiutarti a creare le migliori esperienze possibili, abbiamo creato questa presentazione elenco di controllo. Suddivide tutte le cose che pensiamo siano necessarie per un elemento personalizzato ben gestito.

Crea una radice ombra per incapsulare gli stili.

Why? L'incapsulamento degli stili nella radice ombra dell'elemento ne garantisce il funzionamento a prescindere da dove viene utilizzata. Ciò è particolarmente importante se uno sviluppatore vuole posizionare l'elemento all'interno della radice ombra di un altro elemento. Questo vale anche per elementi semplici come una casella di controllo o un pulsante di opzione. Potrebbe essere del caso che gli unici contenuti all'interno della radice ombra siano gli stili le istanze server autonomamente.
Esempio Lo <howto-checkbox>.

Crea la radice ombra nel costruttore.

Why? Il costruttore è quando hai una consapevolezza esclusiva dell'elemento. È il momento giusto per impostare dettagli di implementazione che non vuoi che altri a aggirarle. Questa operazione verrà eseguita in una richiamata successiva, ad esempio connectedCallback, dovrai proteggerti situazioni in cui l'elemento viene scollegato e poi ricollegato al documento.
Esempio Lo <howto-checkbox>.

Posiziona gli eventuali elementi secondari creati dall'elemento nella sua radice ombra.

Why? I componenti secondari creati dall'elemento fanno parte della sua implementazione e devono essere privato. Senza la protezione di una radice shadow, JavaScript esterno potrebbe interferire inavvertitamente con questi bambini.
Esempio Lo <howto-tabs>.

Usa <slot> per proiettare i bambini DOM di luce nel tuo DOM ombra

Why? Consenti agli utenti del tuo componente di specificare i contenuti al suo interno, poiché HTML secondario rende il tuo componente più componibile. Quando un browser non supporta gli elementi personalizzati, i contenuti nidificati rimangono disponibili, visibili e accessibili.
Esempio Lo <howto-tabs>.

Imposta uno stile di visualizzazione :host (ad es. block, inline-block, flex) a meno che tu non preferisca il valore predefinito di inline.

Why? Gli elementi personalizzati sono display: inline per impostazione predefinita, quindi l'impostazione della loro width o height non avranno alcun effetto. Questo spesso è una sorpresa per gli sviluppatori e potrebbe causare problemi relativi a: il layout della pagina. A meno che tu non preferisca un display inline, deve sempre impostare un valore predefinito per display.
Esempio Lo <howto-checkbox>.

Aggiungi uno stile di visualizzazione :host che rispetti l'attributo nascosto.

Why? Un elemento personalizzato con uno stile display predefinito, ad esempio :host { display: block }, sostituirà la specificità inferiore integrato . hidden. Questo potrebbe sorprenderti se prevedi di impostare hidden dell'elemento per eseguirne il rendering display: none. Inoltre, a uno stile display predefinito, aggiungi il supporto per hidden con :host([hidden]) { display: none }.
Esempio Lo <howto-checkbox>.

Attributi e proprietà

Non sostituire gli attributi globali impostati dall'autore.

Why? Gli attributi globali sono quelli presenti in tutti gli elementi HTML. Alcune esempi includono tabindex e role. Un elemento personalizzato potresti voler impostare il valore iniziale di tabindex su 0 in modo che corrisponda alla tastiera attivabile. Tuttavia, devi sempre verificare prima se lo sviluppatore che utilizza l'elemento lo ha impostato su un altro valore. Ad esempio, se hanno impostato tabindex a -1, è un indicatore che non vogliono che per essere interattivo.
Esempio Lo <howto-checkbox>. Questo aspetto è spiegato più dettagliatamente in Non sostituire l'autore della pagina.

Accetta sempre i dati primitivi (stringhe, numeri, booleani) come attributi o proprietà.

Why? Gli elementi personalizzati, come le controparti integrate, devono essere configurabili. La configurazione può essere trasmessa in modo dichiarativo, tramite attributi o tramite le proprietà JavaScript. Idealmente ogni attributo dovrebbe essere collegato una proprietà corrispondente.
Esempio Lo <howto-checkbox>.

Cerca di mantenere sincronizzati gli attributi e le proprietà dei dati primitivi, rispecchiando le da attribuire e viceversa.

Why? Non puoi mai sapere come un utente interagirà con il tuo elemento. Potrebbero impostare una proprietà in JavaScript e aspettarsi di leggerlo utilizzando un'API come getAttribute(). Se ogni attributo ha un valore una proprietà corrispondente ed entrambe riflettono la, agli utenti di lavorare con il tuo elemento. In altre parole, la chiamata setAttribute('foo', value) deve anche impostare un valore foo e viceversa. Esistono, naturalmente, delle eccezioni questa regola. Non devi riflettere le proprietà dell'alta frequenza, ad esempio currentTime in un video player. Usa il tuo buon senso. Se sembra che un utente interagisca con una proprietà o un attributo non è oneroso rifletterle, allora fallo.
Esempio Lo <howto-checkbox>. Questo aspetto è spiegato più dettagliatamente in Evita problemi di rientro.

Cerca di accettare solo dati avanzati (oggetti, array) come proprietà.

Why? In generale, non esistono esempi di elementi HTML integrati che accettare dati avanzati (matrici e oggetti JavaScript semplici) tramite attributi. Le informazioni aggiuntive vengono invece accettate tramite chiamate al metodo proprietà. Ci sono un paio di svantaggi evidenti nell'accettare informazioni aggiuntive come attributi: può essere costoso serializzare un oggetto di grandi dimensioni in una stringa e tutti i riferimenti agli oggetti andranno persi nel processo di stringificazione. Per ad esempio, se crei una stringa per un oggetto che ha un riferimento a un altro oggetto, o magari un nodo DOM, questi riferimenti andranno persi.

Non riflettere le proprietà dei dati approfonditi negli attributi.

Why? Riflettere le proprietà dei dati dettagliati agli attributi è inutilmente costoso, che richiedono la serializzazione e la deserializzazione degli stessi oggetti JavaScript. A meno che caso d'uso che può essere risolto solo con questa funzione, probabilmente è meglio evitarlo.

Ti consigliamo di controllare le proprietà che potrebbero essere state impostate prima dell'elemento con upgrade eseguito.

Why? Uno sviluppatore che utilizza il tuo elemento potrebbe tentare di impostare una proprietà sull'elemento prima che venga caricata la relativa definizione. Ciò è particolarmente vero se che lo sviluppatore utilizza un framework per gestire i componenti di caricamento, assegnando loro un'etichetta alla pagina e associare le relative proprietà a un modello.
Esempio Lo <howto-checkbox>. Spiegazione più approfondita in Rendi le proprietà lente.

Non presentare domanda autonomamente per i corsi.

Why? Per gli elementi che devono esprimere il proprio stato occorre utilizzare gli attributi. La Generalmente l'attributo class è considerato di proprietà del sviluppatore di usare l'elemento e di scriverci personalmente potrebbe inavvertitamente calpesta le classi degli sviluppatori.

Eventi

Eventi di invio in risposta all'attività dei componenti interni.

Why? Il componente può avere proprietà che cambiano in risposta a un'attività che solo il tuo componente sa, ad esempio, se un timer o un'animazione o il caricamento di una risorsa. È utile inviare eventi in risposta a queste modifiche per notificare all'host che lo stato del componente diverso.

Non inviare eventi in risposta all'impostazione di una proprietà da parte dell'host (verso il basso flusso di dati).

Why? Inviare un evento in risposta all'impostazione di una proprietà da parte dell'host è superfluo (l'host conosce lo stato attuale perché lo ha appena impostato). Eventi di dispacciamento in risposta a un'impostazione dell'host, una proprietà può causare loop infiniti con dati sistemi di associazione.
Esempio Lo <howto-checkbox>.

Video esplicativi

Non sostituire l'autore della pagina

È possibile che uno sviluppatore che utilizza il tuo elemento voglia eseguire l'override di allo stato iniziale. Ad esempio, la modifica dell'ARIA role o della regolabilità con tabindex. Controlla se questi e altri attributi globali sono stati impostati, prima di applicare i tuoi valori.

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

Rendi le proprietà lazy

Uno sviluppatore potrebbe tentare di impostare una proprietà sull'elemento prima che la definizione è stata caricata. Ciò è particolarmente vero se lo sviluppatore utilizza una che gestisce il caricamento dei componenti, inserendoli nella pagina associando le proprie proprietà a un modello.

Nell'esempio seguente, Angular sta associando in modo dichiarativo il valore isChecked alla proprietà checked della casella di controllo. Se la definizione di la casella di controllo "howto" è stata caricata tramite caricamento lento. È possibile che Angular provi a impostare la proprietà selezionata prima dell'upgrade dell'elemento.

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

Un elemento personalizzato dovrebbe gestire questo scenario controllando se alcune proprietà hanno è già stato impostato sulla sua istanza. La <howto-checkbox> dimostra questo pattern utilizzando un metodo chiamato _upgradeProperty().

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

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

_upgradeProperty() acquisisce il valore dell'istanza di cui non è stato eseguito l'upgrade ed elimina la proprietà in modo che non esegua lo shadowing del setter delle proprietà dell'elemento personalizzato. In questo modo, quando la definizione dell'elemento viene caricata, può immediatamente che riflettano lo stato corretto.

Evita problemi di rientro

Si può avere la tentazione di usare attributeChangedCallback() per riflettere lo stato in una proprietà sottostante, ad esempio:

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

Questo può creare un ciclo infinito se il setter delle proprietà riflette anche l'attributo.

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');
}

Un'alternativa è consentire al setter della proprietà di riflettere sull'attributo e fare in modo che il getter determini il proprio valore in base all'attributo.

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

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

In questo esempio, l'aggiunta o la rimozione dell'attributo stabilirà anche la proprietà.

Infine, puoi usare attributeChangedCallback() per gestire gli effetti collaterali come l'applicazione degli stati 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;
    ...
  }
}