DEV Community

Cover image for Quando um framework é melhor que a manipulação nativa do DOM
doug-source
doug-source

Posted on

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

Top comments (0)