1. Introdução
Última atualização:10/08/2021
Componentes da Web
Os componentes da Web são um conjunto de APIs de plataforma da Web que permitem criar novas tags HTML encapsuladas, reutilizáveis e personalizadas para serem usadas em páginas e apps da Web. Os componentes e widgets personalizados, construídos com base nos padrões de componentes da Web, funcionarão em navegadores modernos e podem ser usados com qualquer biblioteca JavaScript ou estrutura que funcione com HTML.
O que é o Lit?
O Lit é uma biblioteca simples para criar componentes da Web rápidos e leves que funcionam em qualquer framework ou sem framework. Com o Lit, é possível criar componentes, aplicativos, sistemas de design e outros recursos compartilháveis.
O Lit fornece APIs para simplificar tarefas comuns dos componentes da Web, como gerenciamento de propriedades, atributos e renderização.
O que você vai aprender
- O que é um componente da Web
- Os conceitos dos Web Components
- Como criar um componente da Web
- O que são lit-html e LitElement
- O que o Lit faz sobre um componente da Web
O que você vai criar
- Um componente da Web baunilha (polegar para cima / negativo)
- Um componente da Web baseado em Lit com "Gostei"/"Não gostei"
O que é necessário
- Qualquer navegador moderno atualizado (Chrome, Safari, Firefox, Chromium Edge). Os componentes da Web funcionam em todos os navegadores modernos, e os polyfills estão disponíveis para o Microsoft Internet Explorer 11 e o Microsoft Edge que não usa cromo.
- Conhecimento sobre HTML, CSS, JavaScript e Chrome DevTools.
2. Configuração e explorando o Playground
Como acessar o código
Ao longo deste codelab, você encontrará links para o playground do Lit, como este:
O Playground é um sandbox de código executado integralmente no navegador. Ele pode compilar e executar arquivos TypeScript e JavaScript, além de resolver automaticamente importações para módulos de nós. Por exemplo:
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://unpkg.com/lit?module';
É possível fazer todo o tutorial no playground do Lit, usando esses checkpoints como pontos de partida. Se você estiver usando o VS Code, poderá usar esses checkpoints para fazer o download do código inicial de qualquer etapa, bem como usá-los para verificar seu trabalho.
Conheça a interface do playground do Lit
A captura de tela da interface do playground do Lit destaca as seções que serão usadas neste codelab.
- Seletor de arquivos. Observe o botão de adição...
- Editor de arquivos.
- Visualização do código.
- Botão "Atualizar".
- Botão "Fazer download".
Configuração do VS Code (avançado)
Confira os benefícios de usar essa configuração do VS Code:
- Verificação do tipo de modelo
- Modelo Intellisense e preenchimento automático
Se você já tem o NPM, o VS Code (com o plug-in lit-plugin) instalado e sabe como usar esse ambiente, basta fazer o download e iniciar esses projetos da seguinte forma:
- Pressione o botão de download.
- Extraia o conteúdo do arquivo .tar em um diretório.
- Instale um servidor de desenvolvimento que possa resolver especificadores de módulo básico (a equipe do Lit recomenda usar @web/dev-server)
- Confira um exemplo
package.json
- Confira um exemplo
- Execute o servidor de desenvolvimento e abra o navegador (se você estiver usando
@web/dev-server
, usenpx web-dev-server --node-resolve --watch --open
)- Se você estiver usando o
package.json
de exemplo, usenpm run serve
.
- Se você estiver usando o
3. Definir um elemento personalizado
Elementos personalizados
Os componentes da Web são uma coleção de quatro APIs nativas da Web. São eles:
- Módulos ES
- Elementos personalizados
- Shadow DOM
- Modelos HTML
Você já usou a especificação de módulos ES, que permite criar módulos JavaScript com importações e exportações carregadas na página com <script type="module">
.
Como definir um elemento personalizado
A especificação de elementos personalizados permite que os usuários definam seus próprios elementos HTML usando JavaScript. Os nomes precisam ter um hífen (-
) para diferenciá-los dos elementos nativos do navegador. Limpe o arquivo index.js
e defina uma classe de elemento personalizado:
index.js
class RatingElement extends HTMLElement {}
customElements.define('rating-element', RatingElement);
Um elemento personalizado é definido pela associação de uma classe que estende HTMLElement
a um nome de tag com hífen. A chamada para customElements.define
instrui o navegador a associar a classe RatingElement
ao tagName ‘rating-element'
. Isso significa que todos os elementos no documento com o nome <rating-element>
serão associados a essa classe.
Coloque um <rating-element>
no corpo do documento e veja o que é renderizado.
index.html
<body>
<rating-element></rating-element>
</body>
Agora, observando a saída, você verá que nada foi renderizado. Isso é esperado, porque você não informou ao navegador como renderizar <rating-element>
. Para confirmar se a definição do elemento personalizado foi concluída, selecione o <rating-element>
nas ferramentas do Chrome Dev. seletor de elementos e, no console, chamando:
$0.constructor
O que deve resultar em:
class RatingElement extends HTMLElement {}
Ciclo de vida do elemento personalizado
Os elementos personalizados vêm com um conjunto de hooks de ciclo de vida. São eles:
constructor
connectedCallback
disconnectedCallback
attributeChangedCallback
adoptedCallback
O constructor
é chamado quando o elemento é criado pela primeira vez, por exemplo, chamando document.createElement(‘rating-element')
ou new RatingElement()
. O construtor é um bom lugar para configurar o elemento, mas é uma prática não recomendada realizar manipulações de DOM no construtor para o elemento "inicialização" de desempenho.
O connectedCallback
é chamado quando o elemento personalizado é anexado ao DOM. Normalmente, é aqui que as manipulações iniciais do DOM acontecem.
O disconnectedCallback
é chamado depois que o elemento personalizado é removido do DOM.
O attributeChangedCallback(attrName, oldValue, newValue)
é chamado quando qualquer um dos atributos especificados pelo usuário muda.
O adoptedCallback
é chamado quando o elemento personalizado é adotado de outro documentFragment
no documento principal usando adoptNode
, como em HTMLTemplateElement
.
Renderizar DOM
Agora, volte para o elemento personalizado e associe um elemento DOM a ele. Defina o conteúdo do elemento quando ele for anexado ao DOM:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
this.innerHTML = `
<style>
rating-element {
display: inline-flex;
align-items: center;
}
rating-element button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
No constructor
, você armazena uma propriedade de instância chamada rating
no elemento. No connectedCallback
, você adiciona filhos do DOM ao <rating-element>
para mostrar a classificação atual com os botões "Gostei" e "Não gostei".
4. Shadow DOM
Por que usar o Shadow DOM?
Na etapa anterior, você notará que os seletores na tag de estilo que você inseriu selecionam qualquer elemento de avaliação na página, bem como qualquer botão. Isso pode fazer com que os estilos vazem do elemento e selecionem outros nós que talvez você não pretenda aplicar. Além disso, outros estilos fora desse elemento personalizado podem involuntariamente estilizar os nós dentro do seu elemento personalizado. Por exemplo, tente colocar uma tag de estilo no cabeçalho do documento principal:
index.html
<!DOCTYPE html>
<html>
<head>
<script src="./index.js" type="module"></script>
<style>
span {
border: 1px solid red;
}
</style>
</head>
<body>
<rating-element></rating-element>
</body>
</html>
A saída precisa ter uma caixa de borda vermelha ao redor do período da classificação. Este é um caso trivial, mas a falta de encapsulamento de DOM pode resultar em problemas maiores para aplicativos mais complexos. É aí que entra o Shadow DOM.
Como anexar uma raiz de sombra
Anexe uma shadow Root ao elemento e renderize o DOM dentro dessa raiz:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
Ao atualizar a página, você vai notar que os estilos no documento principal não podem mais selecionar os nós dentro da raiz paralela.
Como você fez isso? Na connectedCallback
, você chamou this.attachShadow
, que anexa uma raiz paralela a um elemento. O modo open
significa que o conteúdo de sombra pode ser inspecionado e que a raiz paralela também pode ser acessada usando this.shadowRoot
. Observe também o componente Web no inspetor do Chrome:
Agora você verá uma raiz paralela expansível que contém o conteúdo. Tudo dentro dessa raiz paralela é chamado de Shadow DOM. Se você selecionar o elemento "rating" nas Ferramentas para desenvolvedores do Chrome e chamar $0.children
, perceberá que ele não retorna filhos. Isso ocorre porque o Shadow DOM não é considerado parte da mesma árvore do DOM que os filhos diretos, mas sim a Shadow Tree.
Light DOM
Um experimento: adicione um nó como filho direto de <rating-element>
:
index.html
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
Atualize a página e você verá que esse novo nó DOM no Light DOM desse elemento personalizado não aparece na página. Isso ocorre porque o Shadow DOM tem recursos para controlar a forma como os nós do Light DOM são projetados no shadow DOM usando elementos <slot>
.
5. Modelos HTML
Por que usar modelos?
O uso de innerHTML
e strings literais de modelo sem limpeza pode causar problemas de segurança com a injeção de script. Anteriormente, os métodos incluíam o uso de DocumentFragment
s, mas eles também apresentam outros problemas, como carregamento de imagens e scripts executados quando os modelos são definidos, além de introduzir obstáculos para a reutilização. É aqui que o elemento <template>
entra em cena. Os modelos oferecem DOM inerte, um método de alto desempenho para clonar nós e modelos reutilizáveis.
Como usar modelos
Em seguida, faça a transição do componente para usar modelos HTML:
index.html
<body>
<template id="rating-element-template">
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating"></span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
</template>
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
</body>
Aqui você moveu o conteúdo do DOM para uma tag de modelo no DOM do documento principal. Refatore a definição do elemento personalizado:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
const templateContent = document.getElementById('rating-element-template').content;
const clonedContent = templateContent.cloneNode(true);
shadowRoot.appendChild(clonedContent);
this.shadowRoot.querySelector('.rating').innerText = this.rating;
}
}
customElements.define('rating-element', RatingElement);
Para usar esse elemento, consulte o modelo, extraia o conteúdo dele e clone esses nós com templateContent.cloneNode
, em que o argumento true
executa um clone profundo. Em seguida, você inicializa o DOM com os dados.
Parabéns, agora você tem um Web Component! Infelizmente, ele ainda não faz nada. Em seguida, adicione algumas funcionalidades.
6. Como adicionar funcionalidades
Vinculações de propriedades
Atualmente, a única maneira de definir a nota no elemento de classificação é construir o elemento, definir a propriedade rating
no objeto e colocá-la na página. Infelizmente, não é assim que os elementos HTML nativos costumam funcionar. Os elementos HTML nativos costumam ser atualizados com mudanças de propriedade e atributo.
Faça com que o elemento personalizado atualize a visualização quando a propriedade rating
mudar, adicionando as seguintes linhas:
index.js
constructor() {
super();
this._rating = 0;
}
set rating(value) {
this._rating = value;
if (!this.shadowRoot) {
return;
}
const ratingEl = this.shadowRoot.querySelector('.rating');
if (ratingEl) {
ratingEl.innerText = this._rating;
}
}
get rating() {
return this._rating;
}
Você adiciona um setter e um getter para a propriedade rating e depois atualiza o texto do elemento de avaliação se ele estiver disponível. Isso significa que, se você definir a propriedade de avaliação no elemento, a visualização será atualizada. faça um teste rápido no console das Ferramentas para Desenvolvedores.
Vinculações de atributos
Agora, atualize a visualização quando o atributo mudar. Isso é semelhante a uma entrada que atualiza a visualização quando você define <input value="newValue">
. Felizmente, o ciclo de vida do componente Web inclui o attributeChangedCallback
. Atualize a nota adicionando as seguintes linhas:
index.js
static get observedAttributes() {
return ['rating'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
}
}
Para que o attributeChangedCallback
seja acionado, é necessário definir um getter estático para RatingElement.observedAttributes which defines the attributes to be observed for changes
. Depois, defina a classificação de forma declarativa no DOM. Faça um teste:
index.html
<rating-element rating="5"></rating-element>
A classificação agora precisa ser atualizada de maneira declarativa.
Funcionalidade do botão
Agora só falta a funcionalidade dos botões. O comportamento desse componente deve permitir que o usuário forneça uma única classificação de voto positivo ou negativo e forneça um feedback visual ao usuário. Você pode implementar isso com alguns listeners de eventos e uma propriedade de reflexão, mas primeiro atualize os estilos para oferecer um feedback visual anexando as seguintes linhas:
index.html
<style>
...
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
No Shadow DOM, o seletor :host
se refere ao nó ou elemento personalizado ao qual a Shadow Root está anexada. Nesse caso, se o atributo vote
for "up"
, o botão "Gostei" ficará verde, mas se vote
for "down", then it will turn the thumb-down button red
. Agora, implemente a lógica para isso criando uma propriedade / atributo de reflexão para vote
de forma semelhante a como você implementou rating
. Comece com as propriedades setter e getter:
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
}
get vote() {
return this._vote;
}
Inicialize a propriedade da instância _vote
com null
no constructor
e, no setter, verifique se o novo valor é diferente. Nesse caso, você ajusta a nota e, o mais importante, reflete o atributo vote
de volta para o host com this.setAttribute
.
Em seguida, configure a vinculação de atributos:
index.js
static get observedAttributes() {
return ['rating', 'vote'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
} else if (attributeName === 'vote') {
this.vote = newValue;
}
}
Novamente, esse é o mesmo processo que você realizou com a vinculação do atributo rating
. Adicione vote
à observedAttributes
e defina a propriedade vote
no attributeChangedCallback
. Por fim, adicione alguns listeners de eventos de clique para dar a funcionalidade dos botões.
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
this._boundOnUpClick = this._onUpClick.bind(this);
this._boundOnDownClick = this._onDownClick.bind(this);
}
connectedCallback() {
...
this.shadowRoot.querySelector('.thumb_up')
.addEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.addEventListener('click', this._boundOnDownClick);
}
disconnectedCallback() {
this.shadowRoot.querySelector('.thumb_up')
.removeEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.removeEventListener('click', this._boundOnDownClick);
}
_onUpClick() {
this.vote = 'up';
}
_onDownClick() {
this.vote = 'down';
}
No constructor
, você vincula alguns listeners de clique ao elemento e mantém as referências. Na connectedCallback
, você detecta eventos de clique nos botões. No disconnectedCallback
, você limpa esses listeners e, nos próprios listeners de clique, define vote
corretamente.
Parabéns, agora você tem um componente Web com todos os recursos. tente clicar em alguns botões! O problema agora é que meu arquivo JS está alcançando 96 linhas, meu arquivo HTML 43 linhas e o código é bastante detalhado e essencial para um componente tão simples. É aí que entra o projeto Lit do Google.
7. HTML lit
Checkpoint do código
Por que lit-html
Em primeiro lugar, a tag <template>
é útil e tem bom desempenho, mas não acompanha a lógica do componente, o que dificulta a distribuição do modelo com o restante da lógica. Além disso, a maneira como os elementos do modelo são usados inerentemente empreendem o código imperativo, o que, em muitos casos, leva a um código menos legível em comparação com os padrões de codificação declarativos.
É aí que entra o lit-html. Lit HTML é o sistema de renderização do Lit que permite escrever modelos HTML em JavaScript e, em seguida, renderizar e renderizar novamente esses modelos com eficiência com dados para criar e atualizar o DOM. É semelhante às populares bibliotecas JSX e VDOM, mas é executado de forma nativa no navegador e com muito mais eficiência em muitos casos.
Como usar Lit HTML
Em seguida, migre o componente da Web nativo rating-element
para usar o modelo Lit, que usa literais de modelos com tag, que são funções que usam strings de modelo como argumentos com uma sintaxe especial. O Lit usa elementos de modelo em segundo plano para oferecer uma renderização rápida, além de alguns recursos de sanitização para garantir a segurança. Comece migrando o <template>
em index.html
para um modelo Lit adicionando um método render()
ao webcomponent:
index.js
// Dont forget to import from Lit!
import {render, html} from 'lit';
class RatingElement extends HTMLElement {
...
render() {
if (!this.shadowRoot) {
return;
}
const template = html`
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
<button class="thumb_down">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
render(template, this.shadowRoot);
}
}
Também é possível excluir o modelo em index.html
. Neste método de renderização, você define uma variável com o nome template
e invoca a função literal do modelo com a tag html
. Você também vai notar que realizou uma vinculação de dados simples dentro do elemento span.rating
usando a sintaxe de interpolação literal do modelo de ${...}
. Isso significa que, eventualmente, não será mais necessário atualizar esse nó de maneira imperativa. Além disso, você chama o método render
iluminado, que renderiza de forma síncrona o modelo na raiz paralela.
Como migrar para sintaxe declarativa
Agora que você eliminou o elemento <template>
, refatore o código para chamar o método render
recém-definido. Você pode começar aproveitando a vinculação de listener de eventos do Lit para limpar o código do listener:
index.js
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
...
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
Os modelos do Lit podem adicionar um listener de eventos a um nó com a sintaxe de vinculação @EVENT_NAME. Nesse caso, você atualiza a propriedade vote
sempre que esses botões são clicados.
Em seguida, limpe o código de inicialização do listener de eventos no constructor
, no connectedCallback
e no disconnectedCallback
:
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
connectedCallback() {
this.attachShadow({mode: 'open'});
this.render();
}
// remove disonnectedCallback and _onUpClick and _onDownClick
Foi possível remover a lógica do listener de clique dos três callbacks e até remover o disconnectedCallback
completamente. Também foi possível remover todo o código de inicialização do DOM do connectedCallback
, fazendo com que ele parecesse muito mais elegante. Isso também significa que você pode se livrar dos métodos de listener _onUpClick
e _onDownClick
.
Por fim, atualize os setters de propriedade para usar o método render
. Assim, o DOM poderá ser atualizado quando as propriedades ou os atributos mudarem:
index.js
set rating(value) {
this._rating = value;
this.render();
}
...
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
// add render method
this.render();
}
Aqui, você removeu a lógica de atualização de DOM do setter rating
e adicionou uma chamada para render
do setter vote
. Agora o modelo ficou muito mais legível, porque agora as vinculações e os listeners de eventos são aplicados.
Atualize a página para ter um botão de classificação funcionando que vai ficar assim quando o voto for pressionado.
8. LitElement
Por que usar o LitElement
Ainda há alguns problemas com o código. Primeiro, se você mudar a propriedade ou o atributo vote
, a propriedade rating
poderá ser modificada, resultando na chamada de render
duas vezes. Apesar das chamadas repetidas de renderização serem essencialmente um ambiente autônomo e eficiente, a VM JavaScript ainda gasta tempo chamando essa função duas vezes de maneira síncrona. Em segundo lugar, é tedioso adicionar novas propriedades e atributos, pois exige muito código boilerplate. É aí que entra a LitElement
!
A LitElement
é a classe de base do Lit para criar componentes da Web rápidos e leves que podem ser usados em frameworks e ambientes. Em seguida, confira o que o LitElement
pode fazer por nós no rating-element
mudando a implementação para usá-lo.
Como usar o LitElement
Comece importando e transformando a classe base LitElement
em uma subclasse do pacote lit
:
index.js
import {LitElement, html, css} from 'lit';
class RatingElement extends LitElement {
// remove connectedCallback()
...
Importe LitElement
, que é a nova classe de base para o rating-element
. Em seguida, você mantém a importação html
e, por fim, css
, que nos permite definir literais de modelo com tag CSS para cálculos CSS, modelos e outros recursos em segundo plano.
Em seguida, mova os estilos do método de renderização para a folha de estilo estática do Lit:
index.js
class RatingElement extends LitElement {
static get styles() {
return css`
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
`;
}
...
É aqui que a maioria dos estilos fica no Lit. O Lit usa esses estilos e usa recursos do navegador, como folhas de estilo construíveis, para fornecer tempos de renderização mais rápidos e transmiti-los pelo polyfill de componentes da Web em navegadores mais antigos, se necessário.
Lifecycle
O Lit introduz um conjunto de métodos de callback do ciclo de vida de renderização, além dos callbacks nativos do componente Web. Esses callbacks são acionados quando as propriedades do Lit declaradas são alteradas.
Para usar esse recurso, é preciso declarar estaticamente quais propriedades vão acionar o ciclo de vida da renderização.
index.js
static get properties() {
return {
rating: {
type: Number,
},
vote: {
type: String,
reflect: true,
}
};
}
// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()
Aqui, você define que rating
e vote
acionarão o ciclo de vida de renderização do LitElement, além de definir os tipos que serão usados para converter os atributos da string em propriedades.
<user-profile .name=${this.user.name} .age=${this.user.age}>
${this.user.family.map(member => html`
<family-member
.name=${member.name}
.relation=${member.relation}>
</family-member>`)}
</user-profile>
Além disso, a flag reflect
na propriedade vote
vai atualizar automaticamente o atributo vote
do elemento host que você acionou manualmente no setter vote
.
Agora que você tem o bloco de propriedades estáticas, pode remover toda a lógica de atualização de atributos e propriedades. Isso significa que é possível remover os seguintes métodos:
connectedCallback
observedAttributes
attributeChangedCallback
rating
(setters e getters)vote
(setters e getters, mas mantêm a lógica de mudança do setter)
Você mantém o constructor
, além de adicionar um novo método de ciclo de vida da willUpdate
:
index.js
constructor() {
super();
this.rating = 0;
this.vote = null;
}
willUpdate(changedProps) {
if (changedProps.has('vote')) {
const newValue = this.vote;
const oldValue = changedProps.get('vote');
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
}
}
// remove set vote() and get vote()
Aqui, basta inicializar rating
e vote
e mover a lógica setter do vote
para o método de ciclo de vida willUpdate
. O método willUpdate
é chamado antes de render
sempre que qualquer propriedade de atualização é alterada, porque o LitElement agrupa as mudanças de propriedade em lotes e torna a renderização assíncrona. Mudanças nas propriedades reativas (como this.rating
) em willUpdate
não vão acionar chamadas desnecessárias de ciclo de vida de render
.
Por fim, render
é um método de ciclo de vida do LitElement que exige o retornar de um modelo do Lit:
index.js
render() {
return html`
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
}
Não é mais necessário verificar a raiz paralela nem chamar a função render
importada anteriormente do pacote 'lit'
.
Seu elemento será renderizado na visualização agora. clique!
9. Parabéns
Parabéns! Você criou um componente da Web do zero e o evoluiu para um LitElement.
O Lit é muito pequeno (menos de 5 KB + compactado com gzip), super rápido e muito divertido de usar código. É possível fazer componentes que serão consumidos por outros frameworks ou você pode criar apps completos com eles.
Agora você sabe o que é um componente da Web, como criá-lo e como o Lit facilita essa criação.
Checkpoint do código
Quer comparar seu código final com o nosso? Compare aqui.
Qual é a próxima etapa?
Confira alguns dos outros codelabs.
- Lit para desenvolvedores do React
- Criar um visualizador de tijolos com o elemento lit
- Criar um componente Stories com um elemento lit
Leia mais
- Tutorial interativo do Lit (em inglês)
- Documentos do Lit (em inglês)
- Open Web Components: uma comunidade de ferramentas e orientação gerenciada pela comunidade.
- WebComponents.dev: cria um componente da Web em todos os frameworks conhecidos
Community
- Lit and Friends Slack: a maior comunidade de componentes da Web
- @buildWithLit no Twitter: a conta do Twitter da equipe que criou o Lit
- Web Components SF: um encontro do Web Components em São Francisco.