Nós da DOM Virtual
O que é DOM virtual
Uma árvore DOM virtual é uma estrutura de dados JavaScript que descreve uma árvore DOM. Ela consiste em nós DOM virtuais aninhados, também conhecidos como vnodes.
Na primeira vez que uma árvore DOM virtual é renderizada, ela é usada como um modelo para criar uma árvore DOM que corresponda à sua estrutura.
Normalmente, as árvores DOM virtuais são recriadas a cada ciclo de renderização, o que geralmente ocorre em resposta a manipuladores de eventos ou a alterações de dados. Mithril.js compara uma árvore vnode com sua versão anterior e modifica apenas os elementos DOM onde há alterações.
Pode parecer ineficiente recriar vnodes com tanta frequência, mas os engines JavaScript modernos conseguem criar centenas de milhares de objetos em menos de um milissegundo. Por outro lado, modificar o DOM é consideravelmente mais custoso do que criar vnodes.
Por essa razão, Mithril.js usa um algoritmo de comparação de DOM virtual sofisticado e altamente otimizado para minimizar a quantidade de atualizações do DOM. Mithril.js também gera estruturas de dados vnode cuidadosamente elaboradas, que são compiladas por engines JavaScript para um desempenho de acesso a estruturas de dados quase nativo. Além disso, Mithril.js otimiza agressivamente a função que cria vnodes.
Mithril.js se esforça para suportar um modelo de renderização que recria toda a árvore DOM virtual a cada renderização, a fim de fornecer uma API declarativa de modo imediato, um estilo de renderização que facilita drasticamente o gerenciamento da complexidade da UI.
Para ilustrar por que o modo imediato é tão importante, considere a API DOM e o HTML. A API DOM é uma API imperativa de modo retido e requer 1. escrever instruções exatas para montar uma árvore DOM processualmente e 2. escrever outras instruções para atualizar essa árvore. A natureza imperativa da API DOM significa que você tem muitas oportunidades de micro-otimizar seu código, mas também significa que você tem mais chances de introduzir bugs e tornar o código mais difícil de entender.
Em contraste, o HTML está mais próximo de um sistema de renderização de modo imediato. Com HTML, você pode escrever uma árvore DOM de uma forma muito mais natural e legível, sem se preocupar em esquecer de anexar um filho a um pai, encontrar estouros de pilha ao renderizar árvores extremamente profundas, etc.
O DOM virtual avança ainda mais em relação ao HTML, permitindo que você escreva árvores DOM dinâmicas sem ter que escrever manualmente vários conjuntos de chamadas de API DOM para sincronizar eficientemente a UI com alterações de dados arbitrárias.
Noções básicas
Nós DOM virtuais, ou vnodes, são objetos JavaScript que representam elementos DOM (ou partes do DOM). O mecanismo DOM virtual do Mithril.js utiliza uma árvore de vnodes para gerar uma árvore DOM.
Os vnodes são criados através da função utilitária m()
:
m('div', { id: 'test' }, 'hello');
Hyperscript também pode consumir componentes:
// define um componente
var ExampleComponent = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// consuma-o
m(ExampleComponent, { style: 'color:red;' }, 'world');
// HTML equivalente:
// <div style="color:red;">Hello world</div>
Estrutura
Nós DOM virtuais, ou vnodes, são objetos JavaScript que representam um elemento (ou partes do DOM) e têm as seguintes propriedades:
Propriedade | Tipo | Descrição |
---|---|---|
tag | String|Object | O nome do nó de um elemento DOM. Também pode ser a string [ se um vnode for um fragmento, # se for um vnode de texto ou < se for um vnode HTML seguro. Além disso, pode ser um componente. |
key | String? | O valor usado para mapear um elemento DOM para seu respectivo item em um array de dados. |
attrs | Object? | Um mapa hash de atributos DOM, eventos, propriedades e métodos de ciclo de vida. |
children | (Array|String|Number|Boolean)? | Na maioria dos tipos de vnode, a propriedade children é um array de vnodes. Para vnodes de texto e HTML seguros, a propriedade children é uma string, um número ou um booleano. |
text | (String|Number|Boolean)? | Isso é usado em vez de children se um vnode contiver um nó de texto como seu único filho. Isso é feito por razões de desempenho. Os vnodes de componente nunca usam a propriedade text mesmo que tenham um nó de texto como seu único filho. |
dom | Element? | Aponta para o elemento que corresponde ao vnode. Esta propriedade é undefined no método de ciclo de vida oninit . Em fragmentos e vnodes HTML seguros, dom aponta para o primeiro elemento do intervalo. |
domSize | Number? | Isso é definido apenas em fragmentos e vnodes HTML seguros, e é undefined em todos os outros tipos de vnode. Ele define o número de elementos DOM que o vnode representa (começando pelo elemento referenciado pela propriedade dom ). |
state | Object? | Um objeto que é persistido entre renderizações. Ele é fornecido pelo mecanismo central quando necessário. Em vnodes de componente POJO, o state herda prototipicamente do objeto/classe do componente. Em vnodes de componente de classe, é uma instância da classe. Em componentes de closure, é o objeto retornado pelo closure. |
events | Object? | Um objeto que é persistido entre renderizações e que armazena manipuladores de eventos para que possam ser removidos usando a API DOM. A propriedade events é undefined se não houver manipuladores de eventos definidos. Esta propriedade é usada apenas internamente pelo Mithril.js, não use ou modifique-a. |
instance | Object? | Para componentes, um local de armazenamento para o valor retornado pela view . Esta propriedade é usada apenas internamente pelo Mithril.js, não use ou modifique-a. |
Tipos de Vnode
A propriedade tag
de um vnode determina seu tipo. Existem cinco tipos de vnode:
Tipo de Vnode | Exemplo | Descrição |
---|---|---|
Elemento | {tag: "div"} | Representa um elemento DOM. |
Fragmento | {tag: "[", children: []} | Representa uma lista de elementos DOM cujo elemento DOM pai também pode conter outros elementos que não estão no fragmento. Ao usar a função auxiliar m() , os vnodes de fragmento só podem ser criados aninhando arrays no parâmetro children de m() . m("[") não cria um vnode válido. |
Texto | {tag: "#", children: ""} | Representa um nó de texto DOM. |
HTML Seguro | {tag: "<", children: "<br>"} | Representa uma lista de elementos DOM a partir de uma string HTML. |
Componente | {tag: ExampleComponent} | Se tag for um objeto JavaScript com um método view , o vnode representa o DOM gerado pela renderização do componente. |
Tudo em uma árvore DOM virtual é um vnode, incluindo texto. O utilitário m()
normaliza automaticamente seu argumento children
e transforma strings em vnodes de texto e arrays aninhados em vnodes de fragmento.
Apenas nomes de tag de elemento e componentes podem ser o primeiro argumento da função m()
. Em outras palavras, [
, #
e <
não são argumentos de selector
válidos para m()
. Vnodes HTML seguros podem ser criados via m.trust()
Classe monomórfica
O módulo mithril/render/vnode
é usado pelo Mithril.js para gerar todos os vnodes. Isso garante que os engines JavaScript modernos possam otimizar a comparação de DOM virtual sempre compilando vnodes para a mesma classe oculta.
Ao criar bibliotecas que emitem vnodes, você deve usar este módulo em vez de escrever objetos JavaScript brutos para garantir um alto nível de desempenho de renderização.
Evite anti-padrões
Evite reutilizar vnodes
Vnodes devem representar o estado do DOM em um determinado ponto no tempo. O mecanismo de renderização do Mithril.js assume que um vnode reutilizado não foi alterado. Portanto, modificar um vnode que foi usado em uma renderização anterior resultará em comportamento indefinido.
É possível reutilizar vnodes no lugar para evitar uma comparação, mas é preferível usar o onbeforeupdate
.
Evite passar dados do modelo diretamente para componentes via atributos
A propriedade key
pode aparecer em seu modelo de dados de forma a conflitar com a lógica de chave do Mithril.js, e seu modelo pode ser uma instância mutável com um método que compartilha um nome com um hook de ciclo de vida como onupdate
ou onremove
. Por exemplo, um modelo pode usar uma propriedade key
para representar uma chave de cor personalizável. Quando isso muda, pode resultar em componentes recebendo dados incorretos, alterando posições inesperadamente ou outro comportamento inesperado e indesejado. Em vez disso, passe-o como um atributo para que o Mithril.js não o interprete mal (e para que você ainda possa potencialmente mutá-lo ou chamar métodos de protótipo nele mais tarde):
// Modelo de dados
var users = [
{ id: 1, name: 'John', key: 'red' },
{ id: 2, name: 'Mary', key: 'blue' },
];
// Mais tarde...
users[0].key = 'yellow';
// EVITE
users.map(function (user) {
// O componente para John será destruído e recriado
return m(UserComponent, user);
});
// PREFIRA
users.map(function (user) {
// A chave é extraída especificamente: o modelo de dados recebe sua própria propriedade
return m(UserComponent, { key: user.id, model: user });
});
Evite declarações em métodos de visualização
As declarações JavaScript em métodos de visualização geralmente alteram a estrutura naturalmente aninhada de uma árvore HTML, o que torna o código mais extenso e menos legível.
// EVITE
var BadListComponent = {
view: function (vnode) {
var list = [];
for (var i = 0; i < vnode.attrs.items.length; i++) {
list.push(m('li', vnode.attrs.items[i]));
}
return m('ul', list);
},
};
Em vez disso, prefira usar expressões JavaScript, como o operador ternário para renderização condicional e métodos Array para estruturas do tipo lista.
// PREFIRA
var BetterListComponent = {
view: function (vnode) {
return m(
'ul',
vnode.attrs.items.map(function (item) {
return m('li', item);
})
);
},
};