Web Components, o que é ShadowDOM (parte 3)
Tags: javascript, webdev, webcomponents, frontend
Published: 2021-03-21 19:42:06

Esta é a terceira parte da série de tutoriais sobre Web Components, não deixe de ver a [primeira](https://dev.to/gabrieljm/web-components-o-que-sao-e-como-criar-parte-1-ale) e [segunda](https://dev.to/gabrieljm/web-components-atributos-e-metodos-de-reacao-parte-2-g6h) partes. Nesse tutorial irei falar sobre o que é shadowDOM, quais são suas características e as vantagens de usar.

O ShadowDOM é como um DOM normal, porém o que o diferencia do DOM é a forma como ele é criado e como ele se comporta em relação ao resto da página. Com o shadowDOM você cria uma árvore DOM com o escopo atrelado ao elemento, mas separado dos seus possíveis filhos. Essa árvore é chamada de `shadow tree` e o elemento ao qual ele é atrelado é chamado de `shadow host`. Tudo que for adicionado a uma sombra (shadow tree) se torna local relacionado ao elemento hospedeiro (host), incluindo elementos `<style>` e é graças a isso que é possível ter também um CSS com escopo.

Podemos listar assim, alguns dos benefícios de se utilizar o shadowDOM:

- **DOM interna isolada**: Todo o conteúdo DOM do elemento é encapsulado, se tentar fazer um `document.querySelector` não será possível acessar os elementos internos do componente.
- **CSS com escopo**: Todo estilo CSS definido dentro do shadowDOM é mantido dentro dele, sem afetar o lado de fora e sem ser muito afetado pelo lado de fora também. Mas não se preocupe vamos ver mais depois sobre estilização de componentes com shadowDOM.
- **Pensamento em componentes**: Em conjunto com a API de `customElements` o shadowDOM permite ainda mais projetarmos uma interface separada por componentes, provendo reusabilidade e um foco maior em detalhes.

## Criando um shadowDOM

Ao invocar `element.attachShadow()` será atrelado um `shadow root` a esse elemento, independente se ele é ou não um elemento customizado e é assim que um elemento ganha sua shadowDOM.

```jsx
const div = document.createElement('div')
div.attachShadow({ mode: 'open' })
div.shadowRoot.innerHTML = '<style>p {color: blue}</style>  <p>Has ShadowDOM</p>'

document.body.append(div)
```

Ao adicionar esse código e outro elemento `p` ao seu HTML verá que somente o parágrafo dentro da `div`, que possui um shadowDOM, será afetado pelo CSS dentro da tag `style`.

Sobre o objeto passado como atributo para o `attachShadow`, note que ele possuí o atributo `mode` que irá definir como o shadowDOM desse elemento irá se comportar. Pode ser passado `open` e `closed` como valores para o `mode`, por enquanto use sempre **open** depois explicarei sobre a diferença entre eles.

É bom avisar que nem todo elemento nativo do HTML pode ter um shadowDOM atrelado a ele. Pode ser porque esse elemento já possui sua própria shadowDOM (`textarea`, `input`) ou que não faz sentido esse elemento possuir um shadowDOM (`img`).

### Criando um shadowDOM para elementos customizados

O shadowDOM é ótimo de se utilizar em conjunto com elementos customizados. Ele irá permitir que o elemento possua seu próprio HTML, CSS e JS, produzindo assim o que seria um **Web Component**.

Exemplo de como um elemento customizado vincula um shadowDOM a ele mesmo:

```jsx
customElements.define('my-article', class extends HTMLElement {
  constructor() {
    super()

    this.attachShadow({ mode: 'open' })
    this.shadowRoot.innerHTML = `
      <style>
        /* a estilação tem como escopo o próprio elemento! */
      </style>
      <h2>Titulo</h2>
      <p>Texto</p>
    `
  }
})
```

Lembrando que tudo que tiver dentro da tag `<style>` afeta apenas os elementos dentro do **shadow root** do elemento que esta sendo criado.

## Composição e slots

A composição é algo muito importante no desenvolvimento web, é usando esse tipo de conceito que elementos como `<select>`, `<form>`, `<details>`, `<video>` funcionam, eles aceitam outros elementos como filhos e sabem interpretar cada filho da maneira correta e nós podemos fazer esse tipo de composição em nossos componentes usando `slots`.

Mas antes de irmos adiante com os slots, precisamos ver sobre `light DOM`.

### Light DOM

Todo HTML que existente fora do conteúdo interno de um shadowDOM está presente no **light DOM**. Mesmo que esses elementos sejam filhos de um Web Component.

```html
<!-- A div está no light DOM -->
<div>Texto</div>

<!--
  my-element possui um shadowDOM,
  porém os elementos span e button estão no light DOM
-->
<my-element>
  <span>texto</span>
  <button>Clique aqui</button>
</my-element>
```

No shadowDOM de um elemento podemos definir então `slots` para que possamos "capturar" esses elementos do light DOM para o shadowDOM do componente.

Além disso, como havia comentado antes, o CSS de um Web Component com shadowDOM tem o próprio elemento como escopo, mas o elementos vindos do light DOM podem sofrer alterações externas antes de entrar no shadowDOM do elemento. Contudo, nós vamos entrar em mais detalhes depois sobre as diferentes formas de lidar com CSS em um Web Component com shadowDOM.

### O elemento slot

O Shadow DOM é capaz de compor vários trechos de marcação HTML utilizando elementos `<slot>`. Os slots são como marcadores que guardam lugar para outras árvores DOM e que os usuários do componente podem usar para compor o elemento como necessário. Essencialmente, ao definir elementos `<slot>` você permite que marcações externas façam parte do shadowDOM daquele elemento. É como se os elementos recebessem uma permissão de passar pelas barreiras do shadowDOM. Esses elementos são chamados de **distributed nodes** (nós distribuídos).

Um elemento pode definir um ou mais slots no seu shadowDOM. Os slots podem estar vazios ou possuindo um conteúdo de fallback(substituto), que no caso são usados caso o usuário não defina nenhum conteúdo para o slot, ai é utilizado esse conteúdo de fallback.

```html
<!-- slot padrão. Se houver mais de um será usado apenas o primeiro definido -->
<slot></slot>

<!-- slot padrão com fallback -->
<slot>
  <p>Conteúdo de reserva</p>
</slot>
```

Como dito no comentário HTML que está no exemplo, apenas um elemento slot é usado como valor padrão. Porém, podemos definir slots nomeados, assim podemos definir onde ficará cada conteúdo dentro do shadowDOM e assim é possível usar um slot padrão e vários nomeados. Slots nomeados podem ser vistos como marcados específicos que podem ser referenciados através de um nome. Nós passamos o nome para o slot através do atributo `name`.

Por exemplo, o elemento `my-element`:

```html
#shadow-root
<header>
  <slot name="header">Header</slot>
</header>
<div>
  <slot>
    <p>Content's body</p>
  </slot>
</div>
```

Ao usar o componente sua marcação será algo semelhante a isso:

```html
<my-element>
  <h2 slot="header">Meu Titulo</h2>
  <p>Meu conteúdo</p>
</my-element>
```

Como você pode ter percebido no exemplo acima, nós definimos qual slot será usado adicionando um atributo `slot` no elemento, o valor desse atributo deve ser o mesmo do nome declarado no slot do componente. E todo elemento que não possuir o atributo `slot` irá para o primeiro slot sem nome, se houver algum, que foi declarado no componente.

Dessa maneira podemos deixar nossos componentes muito customizáveis e flexíveis. Mas tome cuidado para não deixa-lo mais flexível do que o ideal!

Você deve estar pensamento ou então se você já testou esse código, você viu que na parte do CSS ele não funciona ou parece confuso a forma como ele funciona. Como comentei na parte sobre **Light DOM**, os elementos que são externos (que estão no light DOM) são afetados pelo CSS externo e não o CSS interno do componente. Contudo, a questão do CSS é um assunto que pode ser extenso, graças a isso deixarei para falar mais sobre ele no próximo post.

---

## Referências

[https://developers.google.com/web/fundamentals/web-components/shadowdom](https://developers.google.com/web/fundamentals/web-components/shadowdom)

## Conclusão

E assim você viu como o Shadow DOM pode ser muito vantajoso ao se usar elementos customizados, mas mesmo assim queria dizer que não preciso usá-lo sempre, é possível que você se encontre em uma situação que elementos sem um shadowDOM resolvem melhor o problema. O importante é você conhecer bem cada coisa para saber a hora certa de usar. Espero que tenha gostado, qualquer dúvida deixe um comentário e até logo!!!