Por que acesso direto ao DOM deve ser evitado em projetos com Virtual DOM

Você sabe o perigo que está correndo? Se não, leia este artigo!

Roberto Oliveira
Roberto Oliveira Dev
7 min readAug 18, 2018

--

Enquanto isso, em algum projeto Vue em algum lugar…

Todo mundo tá sabendo que a WEB avançou bastante nos últimos anos com a possibilidade da componentização e com os avanços de UX que isso proporcionou, por meio de tecnologias nativas ou por meio de frameworks e bibliotecas, para criação de single page applications.

Essas bibliotecas de criação de componentes, como Vue, React e afins, nos dão a possibilidade de reutilizar um componente criado, aplicá-lo em múltiplos lugares ou várias vezes em um mesmo lugar. Dessa forma conseguimos velocidade de desenvolvimento e tal…

Observo um erro que muitos programadores estão cometendo; usar getElementById (e tantas outras formas de acessar diretamente o DOM) nos seus projetos que oferecem virtual DOM. O simples uso indiscriminado dessa abordagem denuncia algo ruim para o programador e para o projeto: Confusão ou falta de entendimento nos conceitos de DOM e virtual DOM que uma lib de componentes tem.

Muitas vezes o desenvolvedor acaba cometendo anti-patterns por desconhecer o relacionamento entre o DOM e o virtual DOM, escrevendo código que gera inconsistência de informação e que, consequentemente, irá gerar bugs

Antes de falar sobre o virtual DOM, só vamos recapitular bem superficialmente o que é o DOM (bem superficialmente mesmo, pois o tempo de vocês é precioso).

Document Object Model

No dia a dia do trabalho, em meio a pressão das entregas, às vezes o programador front end não para pra refletir que está criando uma estrutura de dados em árvore e que deve ser muito bem trabalhada. Árvore na disciplina de estrutura de dados é uma estrutura não linear, sendo muito boa para definir hierarquização dos dados, acoplando nós de dados à outros nós filhos, netos e assim por diante…

Estrutura do DOM

Então quando você utiliza um documento de texto (HTML) para descrever a estrutura do site, o navegador interpreta esse HTML e joga pra um objeto árvore em memória; o DOM.

O DOM é um objeto global e não permite componentização (apesar do Shadow DOM permitir, mas não vem ao caso aqui).

Então, em resumo; o DOM é o resultado da interpretação do HTML em memória, sendo um objeto estruturado em árvore.

Visto que hoje em dia a maioria das aplicações que estão saindo são SPA (single page applications) e isso pode forçar muitas sobrescritas no DOM em aplicações mais avançadas, vamos assumir a seguinte premissa que é repetida em muitos lugares quase como um mantra de uma seita:

Alterar o DOM não é performático

Mas por que não é? Na realidade, não é a alteração do DOM em si que não é performática e sim a operação de render do navegador que é blocante e as ferramentas de componentização mais antigas como Angular 1 não sabiam lidar com isso muito bem. O que causa lentidão é o excesso de re-renders que o navegador tem que executar durante as exageradas mudanças no DOM e isso deve ser administrado. Mas aí alguém algum dia parou e falou: “E se a gente criasse um mecanismo performático onde os ‘nós do DOM’ tivessem observáveis (Observable) para avisar exatamente quais foram as mudanças no DOM e tão somente aplicá-las?”. Esse mecanismo é o virtual DOM.

Virtual DOM

Virtual DOM é uma abstração do DOM

Se o DOM é um primeiro objeto em memória, o virtual DOM é um segundo objeto em memória que abstrai o primeiro e, por meio da biblioteca, fornece uma interface de manipulações para ganho de performance.

Mecanismo de diffs

Virtual DOM nada mais é que um objeto Javascript com mecanismos de alteração e aplicação de diffs para atualizar o DOM o mínimo possível e apenas nos nós que sofreram alteração. Dessa forma, a perda de performance em projetos altamente abstraídos é minimizada. Bibliotecas JS modernas, implementam algoritmos complexos de otimização de diferenciação entre o DOM e o virtual DOM e, além disso, permitem criar componentes reutilizáveis independentes entre si, mas que podem não ser tão independentes assim se mal implementados.

GetElementById (e outras formas de acessar o DOM diretamente)

Existem diversos riscos ao tocar o DOM diretamente quando se usa uma lib de virtualização de DOM.

Muitas vezes o desenvolvedor acaba cometendo anti-patterns por desconhecer o relacionamento entre o DOM e o virtual DOM, escrevendo código que gera inconsistência de informação e que, consequentemente, irá gerar bugs:

Inconsistência entre o DOM e o virtual DOM

O Virtual DOM reflete no DOM, mas o DOM pode não refletir no virtual DOM

Quando você opera sobre o DOM diretamente em uma lib que virtualiza o DOM, isso pode gerar inconsistência entre a informação e a abstração da mesma. Essa inconsistência pode prejudicar a reatividade do seu componente. Se você não usa a interface reativa que a biblioteca provê, logo, você não pode reclamar quando seu componente deixar de ser reativo (falei e saí correndo).

Pare pra pensar, se o DOM e virtual DOM são objetos em memória, o são porque guardam informação, mas, note que, guardam informação sobre o mesmo domínio, porém um objeto existe em função do outro. O virtual DOM existe pra te auxiliar a atualizar o DOM de uma boa forma, então atualize o virtual DOM e deixe as coisas fluírem…

Escolha apenas uma fonte da verdade e siga por ela.

Dados globais (estáticos) vs dados de instância encapsulados

Usando a Orientação a Objetos pra clarificar um pouco; no modelo clássico, uma Classe é criada para fornecer um modelo para criação de Instâncias.

Quando você escreve um componente em uma lib como o Vue ou React, é como se você descrevesse uma Classe em Orientação à Objetos e quando você utiliza esse componente, é como se você tivesse instanciando essa Classe, criando assim um novo Objeto.

A descrição do componente (classe) gera a sua (re)utilização com valores de instância

Estaticidade é o que é comum à todas Instâncias, por que diz respeito à Classe, os proptypes de um componente React são um bom exemplo de dados estáticos, pois é a mesma referência pra todas as instâncias. Dados de instância, como o nome explica, diz respeito somente à uma Instância, ao Objeto criado. Por que estou dizendo isso? Porque DOM é informação estática e global. Você não vai querer tocar diretamente nele, pois isso pode gerar efeitos colaterais em todas as instâncias.

Sempre use a interface que a lib te fornece para operar sobre o virtual DOM

Geralmente, em algum caso específico para resolver algum problema não tão bem previsto pela API da biblioteca, você pode ficar tentado à acessar o DOM.

Quando você utiliza o getElementById para pegar/setar informação de um input, por exemplo, está cometendo três erros pelo menos:

  • Acessar diretamente o DOM
  • Utilizar id.
  • Ferir o encapsulamento de informação de uma Instância.

Algo que foi feito para ser global e único não pode ser de instância e reutilizável, então você terá o mesmo identificador para todas as instâncias, algo estático que vai levar ao erro de ser usado para modificar algo de instância.

E isso pode gerar os seguintes side-effects:

  • Pegar uma informação de outro input a qual você não desejava
  • Alterar um input que você não quer
  • Gerar inconsistência em memória entre o DOM e o virtual DOM
  • Perder reatividade

PS: A imagem abaixo não diz respeito à implementação do REF. Serve somente para ilustrar a unicidade que ele provê para cada Instância de um componente, ajudando a encapsular os dados internos.

Utilize o mecanismo de referenciação que sua lib provê

Criar um componente com id a fim de acessar o DOM dessa forma é um anti-pattern e deve ser evitado. Vue e React oferecem formas de criar referências pra determinado elemento pra acessar o virtual DOM sem precisarmos tocar no DOM.

As referências são boas, pois são capazes de identificar Instância, ao invés da Classe.

Use ref para garantir sempre que você está mexendo nos dados de instância de um componente.

Concluindo

Tenha sempre em mente que ao criar um componente em Vue, React ou qualquer outra lib que implementa um virtual DOM, você não está criando uma “Classe” estática reutilizável, está criando uma “Classe” que pode ser instanciada diversas vezes, com suas informações internas independentes. Você não está escrevendo HTML, está escrevendo uma marcação baseada para ser utilizada pela lib e transformada em Virtual DOM. Qualquer informação ou operação que faça você mexer diretamente no DOM sem passar pelo Virtual DOM é um anti-pattern.

E mesmo depois de ter lido tudo isso, se você ainda não entendeu o perigo de utilizar getElementById em projetos que usam virtual DOM, deixarei o argumento final:

Pesquisas comprovam que toda vez que um programador usa getElementById em um projeto Vue (e afins), um bebê foca morre de tanto rir.

Foca na foca!

That’s all, folks!

Caso sinta vontade de trocar ideia a respeito, reclamar das minhas piadas ruins, tirar alguma dúvida, dar alguma sugestão, fazer uma crítica ou correção, fique a vontade para comentar.

--

--

Roberto Oliveira
Roberto Oliveira Dev

Frontend Developer. Entusiasta de programação, Testes, React, React Native, Firebase e afins…