Quando um framework é melhor que a manipulação nativa do DOM
Tags: javascript, programming, portugues
Published: 2023-12-09 21:21:25

_**Nota:** apenas traduzi o texto abaixo e postei aqui._

## DOM Manipulation nativa

Digamos que você tenha um formulário simples para adicionar itens a uma lista. Abaixo está uma lista não ordenada vazia (`ul`) onde você adicionará seus "todo items".

```html
<form id="add-todos">
    <label for="new-todo">What do you want to do?</label>
    <input type="text" name="new-todo" id="new-todo">
    <button>Add Todo</button>
</form>

<ul id="todos"></ul>
```

Quando alguém envia o formulário, você deseja adicionar um item de lista (`li`) com o "todo". Talvez você também queira encapsular ele em um button para que eles possam marcá-lo como concluído clicando ou tocando nele.

Também queremos evitar que o formulário tente "submit" ao server com `event.preventDefault()`.

```javascript
// Obtêm o field #new-todo
var todo = document.querySelector('#new-todo');

// Obtêm o container #todos
var items = document.querySelector('#todos');

document.addEventListener('submit', function (event) {
    // Execute apenas quando o 'submitted form' for #add-todos
    if (event.target.id !== 'add-todos') return;

    // Impede que o form seja 'submitting' ao server
    event.preventDefault();

    // Adiciona um novo 'todo item'
    var li = document.createElement('li');
    li.innerHTML = '<button>' + todo.value + '</button>';
    items.appendChild(li);

    // Limpa o field para que o user possa adicionar um outro todo
    todo.value = '';
});
```

## Marcando como concluído

Agora, se alguém clicar em um button, queremos marcar o "todo" como concluído adicionando um `strikethrough`. Também devemos estilizar nossos buttons para que não se pareçam com buttons.

Primeiro, vamos adicionar uma `class` aos nossos `button elements`.

```javascript
document.addEventListener('submit', function (event) {
    // Execute apenas quando o 'submitted form' for #add-todos
    if (event.target.id !== 'add-todos') return;

    // Impede que o form seja 'submitting' ao server
    event.preventDefault();

    // Adiciona um novo 'todo item'
    var li = document.createElement('li');
    li.innerHTML = '<button class="todo">' + todo.value + '</button>';
    items.appendChild(li);

    // Limpa o field para que o user possa adicionar um outro todo
    todo.value = '';
});
```

Agora, podemos adicionar um `click event listener` que alterna uma class `.completed` em nossos buttons quando alguém clica ou toca neles.

```javascript
document.addEventListener('click', function (event) {
    // Somente executa em .todo buttons
    if (!event.target.classList.contains('todo')) return;

    // Toggle a .completed class
    event.target.classList.toggle('completed');
});
```

E finalmente, vamos adicionar um pouco de CSS para estilizar tudo.

```css
.todo {
    background: transparent;
    border: 0;
    color: inherit;
    font-size: 1em;
    margin: 0;
    padding: 0;
}

.todo.completed {
    text-decoration: line-through;
}
```

Isso tem muito desempenho, mas também é um pé no saco.

Essa abordagem para manipulação de DOM tem muito desempenho. Estamos apenas atualizando o que mudou, minimizando a quantidade de trabalho que o navegador precisa fazer para "repaint" e "reflow" a IU.

No entanto, à medida que seu app fica maior e mais complexo, essa abordagem também se torna um pouco chata.

Por exemplo, e se você quisesse...

1.  Deixar as pessoas deletarem todos?
2.  Mostrar uma mensagem quando ainda não existem "todos items" pendentes?
3.  Salvar todos no localStorage e carregá-los na UI no carregamento da página?
4.  Suportar múltiplas listas?

De repente, você precisa ter muito mais consciência da aparência atual da IU e do que precisa mudar para chegar ao estado final desejado.

E é aqui que entram os `frameworks`.

## State-based UI

A razão pela qual os frameworks surgiram não foi para melhorar o desempenho da manipulação do DOM. O objetivo era tornar a UI mais fácil de gerenciar em JavaScript apps maiores.

Frameworks usam algo chamado `state-based UI`.

Com a `state-based UI`, você define seu estado (que é apenas uma palavra sofisticada para seus dados em um determinado momento) como um object com propriedades. Para nosso **todo app**, pode ser assim.

```javascript
var state = {
    todos: [
        {
            item: 'Buy a new wand',
            completed: false
        },
        {
            item: 'Get money from Gringotts',
            completed: true
        }
    ]
};
```

Você define um template que indica a aparência da IU com base nas diferentes propriedades do seu state.

```javascript
var template = function () {
    // Se não houver todos, mostre uma mensagem
    if (state.todos.length < 1) {
        return 'Você ainda não tem qualquer todo items. Crie um usando o form acima.';
    }
    // Cria uma list de todos
    // https://gomakethings.com/using-array.map-to-create-markup-from-an-array-with-vanilla-js/
    return '<ul>' + state.todos.map(function (todo, index) {
        var completed = todo.completed ? 'completed' : '';
        var html =
            '<li>' +
                '<button class="todo ' + completed + '" data-todo="' + index + '">' +
                    todo.item +
                '</button>' +
            '</li>';
        return html;
    }).join('') + '</ul>';
};
```

Quando alguém "submit" um novo item ou toca em um item, você atualiza seu `state object` e depois instrui o framework a renderizar uma versão atualizada da UI.

Aqui está uma versão simples de vanilla JS.

```javascript
document.addEventListener('submit', function (event) {
    // Execute apenas quando o 'submitted form' for #add-todos
    if (event.target.id !== 'add-todos') return;

    // Impede que o form seja 'submitting' ao server
    event.preventDefault();

    // Adiciona um novo todo item
    state.todos.push({
        item: todo.value,
        completed: false
    });

    // Renderiza a UI
    items.innerHTML = template();

    // Limpa o field para que o user possa adicionar um outro todo
    todo.value = '';
});

document.addEventListener('click', function (event) {
    // Somente executa em .todo buttons
    if (!event.target.classList.contains('todo')) return;

    // Obtêm o index do todo item
    var index = event.target.getAttribute('data-todo');

    // Atualiza o item no state
    var completed = event.target.classList.contains('completed') ? false : true;
    state.todos[index].completed = completed;

    // Atualiza a UI
    items.innerHTML = template();
});
```

## DOM Diffing (Diferença de DOM)

A abordagem de `state-based UI` mostrada acima é péssima para o desempenho.

Cada vez que o state é atualizado, você está reconstruindo toda a IU. Isso resulta em muitos "repaints" e "reflows" desnecessários. E é aí que entram os frameworks.

Frameworks como React e Vue fazem algo chamado `DOM Diffing`.

Em vez de atualizar toda a IU, eles comparam o DOM atual com a aparência que deveria ter com base nas alterações de estado. Em seguida, eles atualizam apenas o que precisa ser atualizado – adicionando e removendo classes, injetando ou removendo elementos e assim por diante.

Em outras palavras, frameworks fazem exatamente a mesma coisa que você faz com a manipulação manual do JS DOM, usando os mesmos métodos JS subjacentes e APIs do navegador.

Então... os frameworks têm melhor desempenho do que a manipulação Vanilla JS DOM ou não?

Não, objetivamente não são. Nos bastidores, eles também estão usando a manipulação JS DOM vanilla.

Mas... se você quiser usar a `state-based UI`, então uma biblioteca ou framework pode ter melhor desempenho se a alternativa for renderizar novamente toda a UI todas as vezes.

Frameworks não têm nenhum segredo de desempenho superpoderoso que o "JS normal" não tem. Eles apenas adicionam uma camada de abstração para tornar a construção de sua IU e atualizações mais fáceis (sem dúvida) à medida que o app se torna mais complexo. E essa abstração tem um custo: arquivos JS maiores que demoram mais para serem baixados e são caros para os navegadores analisarem e executarem.

Com nosso "todo list" simples, acho que a manipulação manual do DOM é mais fácil. À medida que um app como esse fica mais complexo, usar um framework pode fazer sentido.

Se você quiser usar `state-based UI`, em vez de 30kb de React ou Vue, você pode usar alternativas menores como [Preact](https://preactjs.com), ou [Svelte](https://svelte.dev), ou minha própria biblioteca [Reef](https://reefjs.com/).

## Fonte

Newsletter de [Go Make Things](https://gomakethings.com/)