Quando um framework é melhor que a manipulação nativa do DOM

dougsource

doug-source

Posted on December 9, 2023

Quando um framework é melhor que a manipulação nativa do DOM

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".

<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>
Enter fullscreen mode Exit fullscreen mode

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().

// 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 = '';
});
Enter fullscreen mode Exit fullscreen mode

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.

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 = '';
});
Enter fullscreen mode Exit fullscreen mode

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

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');
});
Enter fullscreen mode Exit fullscreen mode

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

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

.todo.completed {
    text-decoration: line-through;
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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>';
};
Enter fullscreen mode Exit fullscreen mode

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.

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();
});
Enter fullscreen mode Exit fullscreen mode

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, ou Svelte, ou minha própria biblioteca Reef.

Fonte

Newsletter de Go Make Things

💖 💪 🙅 🚩
dougsource
doug-source

Posted on December 9, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related