DOM: criando, modificando e removendo elementos

1. Introdução à Manipulação do DOM

O DOM (Document Object Model) é a representação em árvore de uma página HTML que o navegador constrói ao carregar um documento. Manipular o DOM significa criar, alterar ou remover nós dessa árvore em tempo real, permitindo que interfaces web sejam dinâmicas e responsivas à interação do usuário.

A manipulação do DOM pode ser feita de duas formas principais: imperativa (JavaScript puro) e declarativa (React). No JavaScript puro, cada operação é explícita: você seleciona um elemento, cria um novo nó e o insere manualmente. No React, você descreve como a interface deve ser em cada estado — o framework resolve as diferenças e aplica as mudanças mínimas necessárias.

Boas práticas de performance: Evite manipulações frequentes que causem reflow (recalculo de layout) e repaint (redesenho). Prefira operações em lote, use DocumentFragment para inserções múltiplas e evite leituras/escritas alternadas de propriedades geométricas.

2. Criando Novos Elementos

O método mais fundamental para criar elementos é document.createElement(), que gera um novo nó HTML sem inseri-lo na página.

// Criando um elemento <div> e um texto
const div = document.createElement('div');
const texto = document.createTextNode('Olá, DOM!');
div.appendChild(texto);

// Inserindo no body
document.body.appendChild(div);

Para inserir elementos em posições específicas, temos várias opções:

const lista = document.getElementById('lista');
const item = document.createElement('li');
item.textContent = 'Item novo';

// No final
lista.appendChild(item);

// No início
lista.prepend(item.cloneNode(true));

// Antes de um elemento específico
const referencia = lista.children[2];
lista.insertBefore(item.cloneNode(true), referencia);

O método append() é mais flexível que appendChild(), aceitando múltiplos nós e strings:

const container = document.getElementById('container');
container.append('Texto direto', document.createElement('hr'), 'Outro texto');

Para inserção rápida de HTML, insertAdjacentHTML() é eficiente e seguro (não reanalisa o elemento pai):

const alvo = document.getElementById('alvo');
alvo.insertAdjacentHTML('beforebegin', '<p>Antes do alvo</p>');
alvo.insertAdjacentHTML('afterend', '<p>Depois do alvo</p>');

3. Modificando Atributos e Conteúdo

Conteúdo textual

Três propriedades principais controlam o conteúdo de um elemento:

const elemento = document.getElementById('exemplo');

// textContent: retorna todo o texto, ignorando tags HTML
elemento.textContent = '<strong>negrito</strong>'; // Exibe literalmente a string

// innerText: considera estilos CSS (mais lento)
elemento.innerText = 'Texto visível';

// innerHTML: interpreta HTML (cuidado com XSS se vier de fonte externa)
elemento.innerHTML = '<strong>Negrito real</strong>';

Atributos

const link = document.querySelector('a');

link.setAttribute('href', 'https://exemplo.com');
link.setAttribute('target', '_blank');

console.log(link.getAttribute('href')); // "https://exemplo.com"
link.removeAttribute('target');

Classes CSS

A API classList é a forma mais limpa e performática de manipular classes:

const card = document.querySelector('.card');

card.classList.add('destaque', 'ativo');
card.classList.remove('inativo');
card.classList.toggle('expandido'); // Adiciona se não existe, remove se existe
console.log(card.classList.contains('destaque')); // true

4. Modificando Estilos e Estrutura

Estilização inline

const box = document.getElementById('box');
box.style.backgroundColor = '#3498db';
box.style.width = '200px';
box.style.cssText = 'background-color: #e74c3c; border-radius: 8px;';

Alterando estrutura

const parent = document.getElementById('parent');
const novoFilho = document.createElement('p');
novoFilho.textContent = 'Substituto';

// Substituir um filho
const antigo = parent.children[0];
parent.replaceChild(novoFilho, antigo);

// Clonar elemento
const cloneSuperficial = antigo.cloneNode(false); // Apenas o nó
const cloneProfundo = antigo.cloneNode(true);     // Nó e descendentes

// Normalizar: mescla nós de texto adjacentes
const container = document.getElementById('container');
container.normalize();

Manipulação de formulários

const input = document.querySelector('input[name="email"]');
input.value = 'usuario@exemplo.com';

const checkbox = document.querySelector('#aceito');
checkbox.checked = true;

const select = document.querySelector('#cidade');
select.value = 'sp'; // Deve corresponder ao value de uma option

5. Removendo Elementos do DOM

removeChild() vs remove()

const pai = document.getElementById('lista');
const filho = pai.children[0];

// Método tradicional (precisa do pai)
pai.removeChild(filho);

// Método moderno (diretamente no elemento)
filho.remove(); // Compatível com navegadores modernos

Limpando conteúdo interno

const container = document.getElementById('container');

// Rápido, mas perde referências a event listeners
container.innerHTML = '';

// Mais seguro: preserva event listeners nos elementos removidos
container.textContent = '';

// Loop de remoção (útil para remover com animação ou cleanup)
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

Esvaziando um container: loop vs substituição

// Abordagem 1: Substituir o nó inteiro (mais performática)
const pai = document.getElementById('pai');
const novoPai = pai.cloneNode(false); // Apenas o nó vazio
pai.parentNode.replaceChild(novoPai, pai);

// Abordagem 2: Loop (preserva referências ao elemento pai)
while (pai.firstChild) {
  pai.removeChild(pai.firstChild);
}

6. Manipulação Eficiente com Fragmentos e Clonagem

DocumentFragment

DocumentFragment é um contêiner leve que não faz parte do DOM ativo. Inseri-lo no DOM não dispara reflow até o momento da inserção:

const fragment = document.createDocumentFragment();
const lista = document.getElementById('lista-grande');

for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i + 1}`;
  fragment.appendChild(li);
}

// Única inserção → único reflow
lista.appendChild(fragment);

Clonagem profunda vs superficial

const original = document.getElementById('template');

// Superficial: apenas o nó, sem filhos
const cloneVazio = original.cloneNode(false);

// Profunda: nó e toda a subárvore
const cloneCompleto = original.cloneNode(true);

// Útil para reutilizar estruturas complexas
document.body.appendChild(cloneCompleto);

Evitando reflows

// Ruim: causa reflow a cada iteração
for (let i = 0; i < 100; i++) {
  document.getElementById('container').innerHTML += `<p>${i}</p>`;
}

// Bom: constrói tudo em lote
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const p = document.createElement('p');
  p.textContent = i;
  fragment.appendChild(p);
}
document.getElementById('container').appendChild(fragment);

Para animações, use requestAnimationFrame para sincronizar com o ciclo de renderização do navegador.

7. Perspectiva React: Abordagem Declarativa

No React, você não manipula o DOM diretamente. O Virtual DOM calcula a diferença entre o estado anterior e o novo, aplicando apenas as mudanças necessárias.

Criando elementos

// React.createElement (sem JSX)
const elemento = React.createElement('h1', { className: 'titulo' }, 'Olá React');

// JSX (recomendado)
const elementoJSX = <h1 className="titulo">Olá React</h1>;

Atualização via estado

function Lista() {
  const [itens, setItens] = React.useState(['Item 1', 'Item 2']);

  const adicionarItem = () => {
    setItens([...itens, `Item ${itens.length + 1}`]);
  };

  const removerItem = (index) => {
    setItens(itens.filter((_, i) => i !== index));
  };

  return (
    <div>
      <button onClick={adicionarItem}>Adicionar</button>
      <ul>
        {itens.map((item, index) => (
          <li key={index}>
            {item}
            <button onClick={() => removerItem(index)}>Remover</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

A propriedade key é essencial para que o React identifique cada elemento de forma única, otimizando a reconciliação do Virtual DOM.

Remoção via estado

No React, "remover" significa simplesmente não incluir o elemento no retorno do componente:

function App() {
  const [mostrar, setMostrar] = React.useState(true);

  return (
    <div>
      <button onClick={() => setMostrar(!mostrar)}>
        {mostrar ? 'Esconder' : 'Mostrar'} saudação
      </button>
      {mostrar && <p>Olá, mundo!</p>}
    </div>
  );
}

O React remove automaticamente o <p> do DOM real quando mostrar se torna false.

Referências