Neste guia veremos o que são web components, quando usar, sua estrutura e também um crash course criando um web component do zero que é um card Pokémon, com vários recursos interessantes.
O que são web components
Web Components são elementos customizados reutilizáveis construídos com diferentes tecnologias para serem utilizados em aplicações web.
Quando usar
Você já desenvolveu com Angular, Vue.js ou React? Uma das grandes vantagens da utilização desses frameworks é a reutilização do código, através de componentes customizados. Agora imagine todo esse poder usando HTML, CSS e JavaScript sem precisar utilizar nenhum framework, é nesse momento que surgem os web components.
Estrutura
Os web components são formados por três principais tecnologias que usadas em conjunto permitem criarmos componentes reutilizáveis mas também encapsulados, evitando conflito de código.
Elementos customizados
Elemento customizado é a possibilidade de criarmos um elemento por exemplo <andre-felizardo></andre-felizardo>
através de APIs do Javascript que permitem definir comportamentos para esse elemento.
Shadow DOM
DOM
Primeiro precisamos entender o que é DOM.
Document Object Model é uma plataforma que representa como as marcações HTML (entre outras) são lidas e organizadas pelo navegador que você usa. Depois que são carregadas (indexadas) as marcações se transformam em elementos de árvore, que podemos manipular via API.
Entender como essa árvore é construída possibilita que possamos criar códigos CSS e Javascript mais performáticos, por exemplo.
Voltando ao Shadow DOM, ele cria um DOM Fantasma, encapsulado (dentro) do elemento customizado que criamos e é renderizado de forma separada do DOM principal. É esta tecnologia que permite manter os recursos do elemento customizado privados, podendo escrever CSS e Javascript sem causar conflitos em outras partes do documento.
Templates HTML
Para escrever os componentes temos os elementos <template>
e <slot>
que permitem criar templates de marcação que não serão exibidos na página mas poderão ser reutilizadas se tornando modelo da estrutura de componentes customizados.
Mas realmente já dá pra usar?
A mais de 6 anos eu leio e ouço que os web components são o futuro, mas atualmente várias das features já funcionam nos browsers, então, web components são o presente! Vamos dar uma olhada no Can I use e ver como está a compatibilidade dos navegadores com essas funcionalidades.
Em geral, web components são adotados por padrão no Firefox (a partir da versão 63), Chrome, Opera e Edge (versão 80). Safari suporta apenas algumas features (ê Apple –‘).
Crash Course – Desenvolvendo Web Components
1. Desenvolvendo primeiro web component
ALERT: Pra acompanhar melhor o tutorial a seguir, é importante que você tenha conhecimento básico em HTML, CSS e Javascript.
1.1 Criar página HTML
- Crie uma pasta para o projeto, dentro crie um arquivo index.html, nele coloque a estrutura básica de uma página HTML.
- Dentro do body coloque um h3 com o texto “Desenvolvendo Web Components”.
- Também dentro do body vamos colocar o nosso elemento customizado
<poke-card></poke-card>
. - Por último vamos chamar um script chamado pokeCard.js que vamos criar sem seguida.
- Não esqueça de alterar o título da página.
1.2 Criando arquivo Javascript
- Crie um arquivo chamado pokeCard.js no mesmo lugar onde criou o index.html.
- No arquivo crie uma class com o nome de PokeCard. Essa classe vai estender o HTMLElement. Ela poderia estender por exemplo HTMLInputElement ou várias outras possibilidades. Mas como vamos criar um componente mais genérico, estenderemos de HTMLElement, que é um elemento genérico.
- Dentro da classe, vamos usar a função construtora. Dentro dela vamos chamar a função super. A palavra-chave super chama o construtor da classe pai (HTMLElement). Em geral, chamar super deve ser a primeira coisa a ser feita dentro da função construtora.
- Depois vamos alterar o conteúdo do nosso elemento customizado, com o comando
this.innerHTML = 'PokeCard';
- Fora das chaves que encapsulam a nossa classe, vamos registrar o nosso elemento customizado, definindo como será sua tag, e como é sua construção com o comando
window.customElements.define('poke-card', PokeCard);
Depois de salvar no navegador, a aparência deve ser essa aqui:
2. Recebendo parâmetros
Primeiro vamos alterar o HTML, para que o nosso componente receba o nome do Pokémon. Adicione no nosso componente um atributo name com o valor Pikachu.
Dentro da nossa classe no Javascript, abaixo da chamada de super() vamos primeiro armazenar o valor passado no atributo em uma constante com o comando const name = this.getAttribute('name')
e depois passar essa constante para o template do nosso componente com this.innerHTML = name
Depois disso o resultado final deve ser esse aqui:
3. Encapsulando o componente
Podemos evoluir o componente da mesma forma como estamos desenvolvendo, porém se não encapsularmos o componente, podemos ter problema de CSS onde por exemplo uma regra interna ao nosso componente alteraria também os elementos externos ao componente. Então antes de começarmos a escrever CSS, vamos encapsular nosso componente, usando Shadow Root.
3.1 Usando Shadow Root
- Vamos começar anexando um Shadow DOM ao nosso componente. Fazemos isso com o comando
this.attachShadow({ mode: 'open'});
Deixe o código antigo, após super() para baixo desses novos comandos. - Após fazer isso, podemos acessar a raiz do nosso Shadow DOM com
this.shadowRoot. Nela vamos adicionar uma cópia do nosso elemento, que
vamos armazenar numa variável chamada template – que ainda não criamos –
com o comando
this.shadowRoot.appendChild(template.content.cloneNode(true));
3.2 Criando o template
Antes da declaração da classe, vamos criar uma constante com o nome de template, e fazer ela receber um novo elemento template com o comando const template = document.createElement('template');
Dentro dessa constante template construiremos nosso HTML e CSS, passando o código usando o comando template.innerHTML
.
- Coloque uma tag style, e coloque uma cor no h3.
- Depois crie uma tag h3 vazia, fora da tag style.
Depois, voltando ao código dentro da nossa função construtora, após receber o atributo nome e armazenar numa constante, vamos jogar o conteúdo dentro do h3 que escrevemos acima com o comando this.shadowRoot.querySelector('h3').innerText = name;
O resultado esperado é:
4. Adicionando imagem e mais um card
Vamos alterar o HTML para nosso card ter uma imagem. Assim como fizemos com o atributo name, crie no HTML do card um atributo avatar e passe nele a URL de uma imagem.
Depois copie o card e coloque o nome de outro Pokémon e outra imagem.
No nosso arquivo Javascript, vamos colocar uma largura para imagem no CSS, e também colocar uma tag img vazia antes do h3 que inserimos anteriormente.
Dentro da nossa função construtora, vamos fazer parecido com o que fizemos com o h3 para inserir a URL na imagem.
Primeiro vamos armazenar o valor do atributo em uma constante com o código const imageURL = this.getAttribute('avatar');
Após vamos inserir o valor dessa constante na imagem com o código this.shadowRoot.querySelector('img').setAttribute('src', imageURL);
E o resultado esperado é esse aqui:
4.1 Melhorando a aparência do card
O objetivo num é focar no CSS, então apenas copie (ou crie sua própria estilização).
5. Utilizando slot
Usar um slot torna nosso componente mais flexível. Utilizaremos ele para adicionar o peso e altura do Pokémon.
No HTML, dentro da nossa tag customizada adicione <span slot="height">0.4 m</>
e <span slot="weight">6.0 kg</span>
. Faça isso para os dois cards. O valor que colocamos dentro do atributo slot é o nome que estamos dando a ele.
No arquivo Javascript, vamos alterar o conteúdo do template. Abaixo do h3 colocaremos uma div com a classe info e dentro dessa div dois paragrafos um para a altura, outro para o peso. Dentro de cada parágrafo escreveremos uma legenda e criaremos um span com o slot se referenciando ao seu conteúdo do HTML.
Tudo correto, a visualização esperada é:
6. Adicionando funções com connectedCallback
Vamos adicionar um botão dentro da div info para esconder ou exibir a info do Pokémon (altura e peso).
Repare na linha 37 (botão) e na linha 19 onde começa o CSS do botão no print abaixo.
6.1 Entendendo o lifecycle
ConnectedCallback é uma função que todo web component tem. Ela é chamada toda vez que o componente é adicionado ao DOM. Dentro dela vamos adicionar um escutador pro evento de clique do botão this.shadowRoot.querySelector('#toggle-info').addEventListener('click', () => this.toggleInfo());
Em paralelo vamos criar a função toggleInfo que chamamos no click. A princípio vamos apenas colocar um console.log dentro da função.
DisconnectedCallback é o oposto da função acima, ela é chamada toda vez que o elemento customizado é removido do DOM. É uma boa prática remover escutadores dentro dessa função, e é isso que vamos fazer com o código this.shadowRoot.querySelector('#toggle-info').removeEventListener();
Com isso feito, clicando no botão do card, ele deve exibir uma mensagem no console.
7. Criando lógica de toggle nas informações
Agora é só a cereja do bolo. Dentro da função construtora abaixo do super() vamos criar uma propriedade da nossa classe com o código this.showInfo = true;
É essa variável que vai controlar quando vamos exibir ou ocultar as informações do Pokémon.
Dentro da função toggleInfo vamos criar uma lógica básica de trocar a propriedade display da div e trocar o texto do botão.
E pronto! Nosso elemento customizado está funcionando lindamente 😉
Eu coloquei esse código também no Github, e se você clicar em commits você pode ver a evolução do codigo, passo a passo, como foi explicado aqui no texto.
Web Components não são o futuro, são o presente.
Dúvidas, sugestão? Deixa aí nos comentários.