Com os elementos personalizados, você pode criar suas próprias tags HTML. Esta lista de verificação abrange as práticas recomendadas para ajudar você a criar elementos de alta qualidade.
Os elementos personalizados permitem estender o HTML e definir suas próprias tags. Eles são
um recurso incrivelmente poderoso, mas também são de baixo nível, o que significa que nem sempre está
claro a melhor forma de implementar seu próprio elemento.
Para ajudar você a criar as melhores experiências possíveis, elaboramos esta
lista de verificação. Ele detalha tudo o que achamos necessário para ser um
elemento personalizado com bom comportamento.
Lista de verificação
Shadow DOM
Criar uma raiz paralela para encapsular estilos. |
Por quê? |
O encapsulamento de estilos na raiz paralela do elemento garante que eles funcionem independentemente de onde forem usados. Isso é especialmente importante se um desenvolvedor quiser colocar seu elemento dentro da raiz paralela de outro. Isso
se aplica até mesmo a elementos simples, como uma caixa de seleção ou um botão de opção. Pode ser que o único conteúdo dentro da raiz paralela sejam os próprios estilos.
|
Exemplo |
O elemento
<howto-checkbox> .
|
Crie sua raiz paralela no construtor.
|
Por quê? |
O construtor é quando você tem conhecimento exclusivo do elemento.
Esse é um ótimo momento para configurar os detalhes de implementação sem que você queira que outros
elementos me atrapalhem. Se você fizer isso em um callback posterior, como o connectedCallback , será necessário proteger contra situações em que seu elemento é removido e, em seguida, reanexado ao documento.
|
Exemplo |
O elemento
<howto-checkbox> .
|
Coloque todos os filhos que o elemento criar na raiz paralela.
|
Por quê? |
Filhos criados pelo seu elemento fazem parte da implementação e precisam ser
particulares. Sem a proteção de uma raiz paralela, o JavaScript externo pode
interferir acidentalmente nesses filhos.
|
Exemplo |
O elemento
<howto-tabs> .
|
Usar <slot> para projetar filhos do light DOM no seu shadow DOM
|
Por quê? |
Permitir que os usuários do componente especifiquem conteúdo nele porque os filhos HTML tornam o componente mais combinável. Quando um navegador não é compatível com elementos personalizados, o conteúdo aninhado permanece disponível, visível e acessível.
|
Exemplo |
O elemento
<howto-tabs> .
|
Defina um estilo de exibição :host (por exemplo, block ,
inline-block , flex ), a menos que você prefira o padrão de
inline .
|
Por quê? |
Por padrão, os elementos personalizados são display: inline . Portanto, definir width ou height não terá efeito. Isso geralmente
é uma surpresa para os desenvolvedores e pode causar problemas relacionados ao
layout da página. A menos que você prefira uma tela inline , sempre defina um valor display padrão.
|
Exemplo |
O elemento
<howto-checkbox> .
|
Adicione um estilo de exibição :host que respeite o atributo oculto.
|
Por quê? |
Um elemento personalizado com um estilo display padrão, por exemplo,
:host { display: block } , vai substituir o
atributo hidden
integrado de especificidade mais baixa.
Isso pode surpreender você se espera definir o atributo hidden
no seu elemento para renderizá-lo como display: none . Além de um estilo display padrão, adicione suporte para hidden com :host([hidden]) { display: none } .
|
Exemplo |
O elemento
<howto-checkbox> .
|
Atributos e propriedades
Não substitua atributos globais definidos pelo autor.
|
Por quê? |
Os atributos globais são aqueles presentes em todos os elementos HTML. Alguns
exemplos incluem tabindex e role . Um elemento personalizado
pode definir o tabindex inicial como 0 para que seja focalizável pelo
teclado. No entanto, sempre verifique primeiro se o desenvolvedor que está usando
o elemento definiu outro valor. Se, por exemplo, a definição tabindex foi definida como -1, é um sinal de que a pessoa não quer que o elemento seja interativo.
|
Exemplo |
O elemento
<howto-checkbox> . Isso é explicado com mais detalhes em
Não substituir o autor da página.
|
Sempre aceite dados primitivos (strings, números, booleanos) como atributos
ou propriedades.
|
Por quê? |
Os elementos personalizados, como seus correspondentes integrados, devem ser configuráveis.
A configuração pode ser transmitida de maneira declarativa, por atributos ou de modo imperativo usando propriedades JavaScript. O ideal é que cada atributo também seja vinculado a uma propriedade correspondente.
|
Exemplo |
O elemento
<howto-checkbox> .
|
Procure manter os atributos e propriedades de dados primitivos em sincronia, refletindo de propriedade para atributo e vice-versa.
|
Por quê? |
Nunca se sabe como um usuário vai interagir com o elemento. Ele pode
definir uma propriedade em JavaScript e esperar ler esse valor
usando uma API como getAttribute() . Se cada atributo tiver uma propriedade correspondente e ambos forem refletidos, facilitará o trabalho dos usuários com seu elemento. Em outras palavras, chamar setAttribute('foo', value) também precisa definir uma propriedade foo correspondente e vice-versa. É claro que há exceções a essa regra. Não inclua propriedades de alta frequência, como currentTime , em um player de vídeo. Use o bom senso. Se parecer que o usuário vai interagir com uma propriedade ou atributo e não for trabalhoso refletir isso, faça isso.
|
Exemplo |
O elemento
<howto-checkbox> . Isso é explicado com mais detalhes em
Evitar problemas de reentrada.
|
Tente aceitar apenas dados avançados (objetos, matrizes) como propriedades.
|
Por quê? |
De modo geral, não há exemplos de elementos HTML integrados que
aceitem dados avançados (objetos e matrizes JavaScript simples) por meio dos
atributos. Os dados avançados são aceitos por chamadas de método ou propriedades. Há algumas desvantagens óbvias em aceitar dados avançados como atributos: pode ser caro serializar um objeto grande para uma string, e todas as referências de objeto serão perdidas nesse processo de string. Por exemplo, se você stringificar um objeto que tem uma referência a outro objeto ou talvez a um nó DOM, essas referências serão perdidas.
|
Não reflete propriedades de dados avançados para atributos.
|
Por quê? |
Refletir propriedades de dados avançados para atributos é desnecessariamente caro, exigindo serializar e desserializar os mesmos objetos JavaScript. A menos que você tenha um caso de uso que só possa ser resolvido com esse recurso, provavelmente é melhor evitá-lo.
|
Verifique se há propriedades que podem ter sido definidas antes do upgrade do elemento.
|
Por quê? |
Um desenvolvedor que usa seu elemento pode tentar definir uma propriedade no elemento antes que a definição seja carregada. Isso é válido principalmente quando o
desenvolvedor usa um framework que processa o carregamento de componentes, os carimbos
na página e a vinculação das propriedades a um modelo.
|
Exemplo |
O elemento
<howto-checkbox> . Mais informações são explicadas em
Tornar as propriedades lentas.
|
Não aplicar turmas automaticamente.
|
Por quê? |
Os elementos que precisam expressar o estado precisam usar atributos. O atributo class geralmente é considerado propriedade do desenvolvedor que usa seu elemento, e gravar nele por conta própria pode acabar pisando nas classes de desenvolvedor.
|
Eventos
Envie eventos em resposta à atividade do componente interno.
|
Por quê? |
O componente pode ter propriedades que mudam em resposta a uma atividade que
somente ele conhece, por exemplo, se um timer ou uma animação
for concluída ou um recurso terminar de carregar. É útil enviar eventos em resposta a essas mudanças para notificar o host de que o estado do componente é diferente.
|
Não envie eventos em resposta à configuração do host de uma propriedade (fluxo de dados para baixo).
|
Por quê? |
O envio de um evento em resposta a uma propriedade do host que define uma propriedade é supérfluo (o host sabe o estado atual porque apenas o definiu). O envio de eventos em resposta a uma configuração de host de uma propriedade pode causar loops infinitos com sistemas de vinculação de dados.
|
Exemplo |
O elemento
<howto-checkbox> .
|
Vídeos de explicação
Não substituir o autor da página
É possível que um desenvolvedor que use seu elemento queira modificar parte
do estado inicial dele. Por exemplo, mudando a ARIA role
ou a focalização com
tabindex
. Verifique se esses e outros atributos globais foram definidos antes de aplicar seus próprios valores.
connectedCallback() {
if (!this.hasAttribute('role'))
this.setAttribute('role', 'checkbox');
if (!this.hasAttribute('tabindex'))
this.setAttribute('tabindex', 0);
Tornar as propriedades lentas
Um desenvolvedor pode tentar definir uma propriedade no seu elemento antes que a
definição dele seja carregada. Isso é válido principalmente quando o desenvolvedor usa um
framework que gerencia componentes de carregamento, inserção na página e
vinculação das propriedades a um modelo.
No exemplo a seguir, o Angular vincula de maneira declarativa a propriedade
isChecked
do modelo à propriedade checked
da caixa de seleção. Se a definição da caixa de seleção de instruções foi carregada lentamente, é possível que o Angular tente definir a propriedade marcada antes do upgrade do elemento.
<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>
Um elemento personalizado precisa lidar com esse cenário verificando se alguma propriedade já
foi definida na instância. A <howto-checkbox>
demonstra esse padrão usando um método com o nome _upgradeProperty()
.
connectedCallback() {
...
this._upgradeProperty('checked');
}
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
_upgradeProperty()
captura o valor da instância sem upgrade e exclui
a propriedade para não ocultar o definidor de propriedades do próprio elemento personalizado.
Dessa forma, quando a definição do elemento for finalmente carregada, ela poderá refletir imediatamente
o estado correto.
Evitar problemas de reentrada
É tentador usar o attributeChangedCallback()
para refletir o estado para uma
propriedade subjacente, por exemplo:
// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'checked')
this.checked = newValue;
}
No entanto, isso pode criar um loop infinito se o setter da propriedade também refletir o atributo.
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');
}
Uma alternativa é permitir que o setter da propriedade reflita o atributo e
fazer com que o getter determine o valor dele com base no atributo.
set checked(value) {
const isChecked = Boolean(value);
if (isChecked)
this.setAttribute('checked', '');
else
this.removeAttribute('checked');
}
get checked() {
return this.hasAttribute('checked');
}
Nesse exemplo, adicionar ou remover o atributo também vai definir a propriedade.
Por fim, o attributeChangedCallback()
pode ser usado para processar efeitos colaterais,
como a aplicação de estados 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;
...
}
}