Quando um framework é melhor que a manipulação nativa do DOM
doug-source
Posted on December 9, 2023
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>
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 = '';
});
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 = '';
});
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');
});
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;
}
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...
- Deixar as pessoas deletarem todos?
- Mostrar uma mensagem quando ainda não existem "todos items" pendentes?
- Salvar todos no localStorage e carregá-los na UI no carregamento da página?
- 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
}
]
};
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>';
};
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();
});
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
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
December 12, 2023