Olá componentes da Web
Se você já ouviu falar sobre Web Components, mas nunca construiu um, você veio ao lugar certo. A hora chegou! Vamos construir um Web Component juntos!
Quando tenho a oportunidade de ensinar às pessoas sobre Web Components, geralmente gosto de começar com algo simples que muitos de nós conhecemos do “mundo real”. Então, vamos construir uma “marca de nome” do Web Component que se pareça com isto…
INFORMAÇÕES: O que é um Web Component? O termo “Componentes Web” é um termo genérico que abrange uma coleção de vários padrões W3C em HTML e CSS, que juntos permitem a criação e compartilhamento de elementos HTML (componentes) nativos e reutilizáveis da plataforma web.
Passo zero
- Comece com um documento HTML básico que tenha um script de módulo JS apontando para um arquivo JavaScript (temporariamente) vazio.
- No corpo do documento, adicione um
name-tagelemento com o texto “Web Components” como seu conteúdo. Este é o componente da Web que gostaríamos de trabalhar eventualmente. - Inicie um servidor web e navegue até seu arquivo HTML. Você deve ver o texto “Web Components” renderizado em seu navegador.
<!DOCTYPE html>
<html>
<head>
<title>Hello Web Components</title>
</head>
<body>
<name-tag greeting="Hola">Web Components</name-tag>
<script type="module" src="index.js"></script>
</body>
</html>
// Nothing here yet. We'll add our Web Component code in the next step.
Passo um
- Em seu arquivo JavaScript, declare o comportamento de seu
name-tagelemento personalizado criando uma classe chamadaNameTagque se estenda deHTMLElement. Todos os Web Components são herdados deHTMLElementelementos internos, comodiv,spaneinput. - Registre seu elemento com o navegador chamando
customElements.define(...), fornecendo o nome de tag HTML desejado e aNameTagclasse como argumentos. - Atualize o navegador e você ainda verá o mesmo texto de antes. No entanto, se você inspecionar o
name-tagelemento, verá que não é apenas umHTMLElement, mas que seu construtor agoraNameTagtambém é.
class NameTag extends HTMLElement {
}
customElements.define('name-tag', NameTag);
Passo dois
- Adicione um construtor à sua classe. Após a chamada para
super(), chamethis.attachShadow(...)para criar um Shadow DOM para seu elemento personalizado. Um Shadow DOM é como ter um documento HTML privado no qual apenas seu componente pode renderizar. Passe as opções{ mode: ‘open’ }para que a raiz sombra e seus elementos internos ainda sejam acessíveis externamente via JavaScript e por meio das ferramentas do desenvolvedor. - Depois que o Shadow DOM estiver anexado, você poderá acessar
this.shadowRoote definir suainnerHTMLpropriedade para o HTML de sua escolha. - Atualize o navegador e observe que
innerHTMLo conteúdo do Shadow DOM agora está sendo renderizado, mas o conteúdo do próprio elemento, o texto “Web Components”, não está mais sendo renderizado. Para onde foi? - Abra o inspetor e verifique se há um
#shadow-rootnó que você pode inspecionar para ver o que você forneceu comoinnerHTML. Observe que seu conteúdo ainda está no DOM também, mesmo que não esteja sendo renderizado. A razão pela qual não está renderizando é porque o navegador não sabe como compor o conteúdo do Light DOM com o Shadow DOM. Vamos consertar isso a seguir.
class NameTag extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = 'Rendering from Shadow DOM';
}
}
customElements.define('name-tag', NameTag);
Passo três
- Revise o HTML que está sendo colocado na raiz de sombra para que inclua um
<slot>elemento. Isso informa ao navegador como compor seu Light DOM e Shadow DOM juntos. O slot fornece um local para “projetar” ou renderizar o conteúdo do Light DOM no Shadow DOM. O conteúdo ainda existe no Light DOM, mas é renderizado como se estivesse no local do slot. - Atualize seu navegador e observe que agora o conteúdo Light e Shadow DOM estão devidamente compostos juntos.
class NameTag extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = 'HELLO <slot></slot>';
}
}
customElements.define('name-tag', NameTag);
Passo Quatro
- Para permitir que nosso
greetingatributo funcione, precisamos informar à plataforma que existe umgreetingatributo que queremos observar. Crie um getter estático chamadoobservedAttributesque retorne uma matriz de nomes de atributos para a plataforma observar. A matriz deve conter o nome do atributo único:['greeting']. - Em seguida, implemente um
attributeChangedCallback()que a plataforma possa invocar sempre que qualquer um de seus atributos observados mudar. - Adicione um getter/setter de propriedade para fornecer acesso de propriedade ao atributo, pois a maioria dos elementos HTML possui propriedades e atributos. Isso garantirá que nosso elemento personalizado pareça com qualquer outra coisa na plataforma e que funcione corretamente com estruturas de front-end populares que definem atributos e propriedades. O getter e o setter podem apenas delegar para as APIs
getAttribute()esetAttribute()doHTMLElement. - Extraia uma
renderfunção que recebe o componente como entrada e chame-aattributeChangedCallback()de modo que ela possa atualizar sua renderização quando o estado do elemento mudar. Remova o código definidoinnerHTMLno construtor. Isso não é mais necessário. - Também podemos introduzir um
connectedCallback().A plataforma chamará isso quando o elemento estiver conectado ao documento. Usaremos isso para garantir que haja um valor padrão casogreetingnão haja um definido no momento em que o elemento for conectado. - Atualize o navegador para ver se o
greetingatributo está entrando em vigor. Experimente usar as ferramentas de depuração para definir agreetingpropriedade e o atributo. Tente colocar pontos de interrupção no arquivoattributeChangedCallback().
const render = x => `${x.greeting.toUpperCase()} <slot></slot>`;
class NameTag extends HTMLElement {
static get observedAttributes() {
return ['greeting'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
get greeting() {
return this.getAttribute('greeting');
}
set greeting(value) {
this.setAttribute('greeting', value);
}
connectedCallback() {
if (!this.greeting) {
this.greeting = 'Hello';
}
}
attributeChangedCallback(name, oldValue, newValue) {
this.shadowRoot.innerHTML = render(this);
}
}
customElements.define('name-tag', NameTag);
- Vamos melhorar nossa
renderfunção para que ela forneça uma estrutura mais realista. - Atualize o navegador para garantir que a nova estrutura seja renderizada corretamente.
const render = x => `
<div part="header" class="header">
<h3 part="greeting">${x.greeting.toUpperCase()}</h3>
<h4 part="message">my name is</h4>
</div>
<div part="body" class="body">
<slot></slot>
</div>
<div part="footer" class="footer"></div>
`;
Passo Seis
- Aproveitando os novos padrões de Constructible StyleSheets e Adopted StyleSheets, crie uma
CSSStyleSheetinstância e chamereplaceSync()para definir seu texto CSS. - Em seu construtor de elemento, anexe seus estilos personalizados ao existente
adoptedStyleSheetsdo arquivoshadowRoot. - Atualize seu navegador para ver um componente totalmente estilizado.
const styles = new CSSStyleSheet();
styles.replaceSync(`
:host {
--default-color: red;
--default-radius: 6px;
--default-depth: 5px;
display: inline-block;
contain: content;
color: white;
background: var(--color, var(--default-color));
border-radius: var(--radius, var(--default-radius));
min-width: 325px;
text-align: center;
box-shadow: 0 0 var(--depth, var(--default-depth)) rgba(0,0,0,.5);
}
.header {
margin: 16px 0;
position: relative;
}
h3 {
font-weight: bold;
font-family: sans-serif;
letter-spacing: 4px;
font-size: 32px;
margin: 0;
padding: 0;
}
h4 {
font-family: sans-serif;
font-size: 18px;
margin: 0;
padding: 0;
}
.body {
background: white;
color: black;
padding: 32px 8px;
font-size: 42px;
font-family: cursive;
}
.footer {
height: 16px;
background: var(--color, var(--default-color));
border-radius: 0 0 var(--radius, var(--default-radius)) var(--radius, var(--default-radius));
}
`);
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.adoptedStyleSheets = [
...this.shadowRoot.adoptedStyleSheets,
styles
];
}
Continue lendo para se aprofundar um pouco mais e descobrir alguns recursos e bibliotecas de aprendizado adicionais.
Indo mais fundo
Propriedades personalizadas CSS (também conhecidas como variáveis CSS)
Uma maneira comum de permitir que os elementos personalizados sejam estilizados é basear os estilos de componente nas propriedades personalizadas CSS (também conhecidas como variáveis CSS). As propriedades personalizadas são declaradas com um prefixo de traço duplo e referenciadas com a var(...)função. Ao fazer referência a uma variável, você também pode fornecer um valor alternativo, que pode ser outra variável. Você pode ver essa técnica usada em todo o CSS acima. Para brincar com isso, crie vários <name-tag>elementos em sua página e use o inspetor de estilo do navegador para definir ——color, ——depthe——radiuspropriedades em elementos individuais ou em elementos pais. Mesmo que o Shadow DOM encapsula estilos, as propriedades personalizadas do CSS “perfuram” o limite do Shadow DOM por padrão. Isso torna possível criar um sistema de temas que funcione em toda uma biblioteca de componentes ou aplicativo. E lembre-se, CSS Custom Properties pode ser usado junto com CSS calc(...)para efeitos surpreendentes.
- Leia mais sobre CSS Custom Properties no MDN.
- Leia mais sobre CSS Calc no MDN.
Os estilos Shadow DOM também podem aproveitar seletores especiais, como o :host seletor que tem como alvo o próprio elemento. É uma prática recomendada configurar estilos de host para o padrão displaye disabledos estados. Verifique containtambém a propriedade CSS para obter maneiras de melhorar o desempenho de renderização do componente. Se você tiver estilos especiais para elementos colocados dentro do conteúdo de seu elemento, poderá especificá-los usando o ::slotted()seletor.
- Leia mais sobre :host no MDN.
- Leia mais sobre ::slotted no MDN.
- Leia mais sobre CSS Contain no MDN.
Como adoptedStyleSheetsainda não está implementado em todos os navegadores (estou olhando para você, Safari!), para qualquer componente de produção que você criar, você desejará detectar recursos e retornar à injeção de elemento de estilo conforme necessário. Isso é algo que muitas bibliotecas de componentes da Web (por exemplo, FAST ) tratam para você automaticamente.
- Leia mais sobre as folhas de estilo adotadas no MDN.
Você deve ter notado que vários elementos no shadow DOM acima possuem um partatributo. Isso permite que um desenvolvedor de componentes da Web declare partes do componente que podem ser estilizadas externamente pelos consumidores do componente. Para experimentar, crie vários <name-tag>elementos em sua página, cada um com um class. Em seguida, crie um CSS direcionado às partes com base em um seletor de classe e ajuste os estilos de saudação. Aqui está o que isso pode parecer:
.large-greeting::part(greeting) {
font-size: 64px;
}
O ciclo de vida do componente da Web
Em nosso NameTagelemento, usamos o connectedCallback(...), que é um dos ganchos padrão do ciclo de vida do Web Component. Mas não é o único. Aqui está uma lista de callbacks de ciclo de vida disponíveis que você pode usar em seus componentes:
constructor()— Executa quando o elemento é criado ou atualizado.connectedCallback()— Executa quando o elemento é inserido no DOM.disconnectedCallback()— Executa quando o elemento é removido do DOM.attributedChangedCallback(attrName, oldValue, newValue)— Executa sempre que um dos atributos personalizados do elemento é alterado.adoptedCallback()— Executa quando o elemento é movido de seu atualdocumentpara um novodocumentpor meio de uma chamada para aadoptNode(…)API.
Como mencionado anteriormente, a quantidade de clichê envolvida na criação de um Web Component simples parece um pouco demais. Isso ocorre porque os padrões de componentes da Web fornecem apenas os recursos de baixo nível, com pouca ou nenhuma opinião incorporada. Mas e se um conjunto leve de opiniões opcionais fosse introduzido? Isso poderia reduzir a quantidade de código e fornecer outros benefícios? Muitas pessoas que constroem componentes da Web pensam assim e, portanto, usam algum tipo de pequena biblioteca auxiliar. Para concluir este post, gostaria de mostrar o mesmo componente, construído com FAST , que agiliza o processo de criação de Web Components e fornece ferramentas adicionais para a construção de soluções mais complexas. Aqui está o NameTagWeb Component implementado com FAST , usando TypeScript:
import {
attr,
css,
customElement,
FASTElement,
html
} from "@microsoft/fast-element";
// Create a reactive template based on the element's state.
const template = html<NameTag>`
<div part="header" class="header">
<h3 part="greeting">${x => x.greeting.toUpperCase()}</h3>
<h4 part="message">my name is</h4>
</div>
<div part="body" class="body">
<slot></slot>
</div>
<div part="footer" class="footer"></div>
`;
// Create styles that automatically use adopted style sheets when present.
const styles = css`
:host {
--default-color: red;
--default-radius: 6px;
--default-depth: 5px;
display: inline-block;
contain: content;
color: white;
background: var(--color, var(--default-color));
border-radius: var(--radius, var(--default-radius));
min-width: 325px;
text-align: center;
box-shadow: 0 0 var(--depth, var(--default-depth)) rgba(0,0,0,.5);
}
.header {
margin: 16px 0;
position: relative;
}
h3 {
font-weight: bold;
font-family: sans-serif;
letter-spacing: 4px;
font-size: 32px;
margin: 0;
padding: 0;
}
h4 {
font-family: sans-serif;
font-size: 18px;
margin: 0;
padding: 0;
}
.body {
background: white;
color: black;
padding: 32px 8px;
font-size: 42px;
font-family: cursive;
}
.footer {
height: 16px;
background: var(--color, var(--default-color));
border-radius: 0 0 var(--radius, var(--default-radius)) var(--radius, var(--default-radius));
}
`;
// Define the element with a name, template, and styles.
@customElement({
name: 'name-tag',
template,
styles
})
class NameTag extends FASTElement { // Remove boilerplate w/ base class.
@attr greeting = 'Hello'; // A reactive HTML attribute with a default.
}
Observe como todo o clichê desaparece? Aqui estão algumas coisas que a FAST está fazendo por você:
- Modelos — O FAST fornece um mecanismo de modelo baseado em reatividade e alto desempenho, incluindo suporte para MVVM avançado em escala.
- Estilos — O FAST detecta automaticamente a presença de folhas de estilo adotadas e as utiliza, se possível. Ele também armazena em cache e reutiliza instâncias de folha de estilo em instâncias de componentes da Web para melhor desempenho e gerenciamento de memória.
- FASTElement — A classe base configura automaticamente o Shadow DOM e se conecta ao ciclo de vida para lidar com a renderização com o modelo fornecido. O decorador fornece uma maneira declarativa de conectar seu modelo, estilos e classe, enquanto os registra na plataforma usando o nome fornecido.
- Atributos — Em vez de ter que declarar manualmente um getter/setter, configurar a
observedAttributesmatriz e manipular valores padrão e retornos de chamada de alteração de atributo, você simplesmente decora um campo@attreFASTElementmanipula tudo isso para você.
Interessado em saber mais ou ingressar na crescente comunidade FAST e Web Components?
- Encontre FAST no GitHub.
- Explore a documentação completa do FAST.
- Inscreva-se no blog FAST.
- Junte-se ao servidor FAST Discord.





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)