No Vue 2, cada atualização executava uma comparação completa entre as árvores de nós virtuais. Com o Vue 3, o compilador insere uma marca de patch (PatchFlag) em cada nodo dinâmico. Durante o diff, o runtime ignora os nós estáticos e inspeciona apenas os que possuem essa marca, reduzindo drasticamente o trabalho de reconciliação.
Como exemplo, em um template contendo elementos estáticos e uma única interpolação reativa, apenas o nó de texto dinâmico recebe a flag TEXT (1). O restante da árvore não é mais revisitado a cada renderização.
<!-- Resultado da compilação -->
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("section", null, [
_createVNode("h1", null, "Painel"),
_createVNode("p", null, "Bem-vindo(a)"),
_createVNode("span", null, _toDisplayString(_ctx.usuario) + " !", 1 /* TEXT */)
]))
}
- Elevação de estáticos (hoistStatic)
Elementos que nunca mudam podem ser criados uma única vez e reutilizados em todas as renderizações. O compilador do Vue 3 identifica esses nós e os move para o escopo do módulo, fora da função render. Assim, a cada ciclo reativo, a função apenas referencia os nós já existentes em vez de recriá-los.
Veja a diferença entre a saída sem e com a otimização:
<!-- Antes da elevação -->
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("section", null, [
_createVNode("h1", null, "Painel"),
_createVNode("p", null, "Bem-vindo(a)"),
_createVNode("span", null, _toDisplayString(_ctx.usuario) + " !", 1 /* TEXT */)
]))
}
<!-- Após a elevação -->
const _staticNode_1 = /*#__PURE__*/ _createVNode("h1", null, "Painel", -1 /* HOISTED */)
const _staticNode_2 = /*#__PURE__*/ _createVNode("p", null, "Bem-vindo(a)", -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("section", null, [
_staticNode_1,
_staticNode_2,
_createVNode("span", null, _toDisplayString(_ctx.usuario) + " !", 1 /* TEXT */)
]))
}
- Cache de listeners de eventos
Listeners definidos diretamente no template, como @click="salvar", normalmente seriam tratados como propriedades dinâmicas e verificados a cada atualização. A opção cacheHandlers envolve a função em uma referência do cache interno, evitando que ela seja reavaliada ou passada novamente para o DOM quando nada mudou.
<!-- Antes do cache -->
_createVNode("button", {
onClick: _ctx.salvar
}, "Confirmar", 8 /* PROPS */, ["onClick"])
<!-- Após o cache -->
_createVNode("button", {
onClick: _cache[0] || (_cache[0] = (...args) => _ctx.salvar && _ctx.salvar(...args))
}, "Confirmar")
Quando a função referenciada é a mesma, nenhum rastreamento adicinoal é necessário e o listener é reaproveitado.
- Renderização SSR
Em aplicações renderizadas no servidor, trechos inteiros de HTML puramente estáticos são empurrados para um buffer commo string. Bindings dinâmicos são interpolados diretamente nessa string, sem passar pela criação de objetos de virtual DOM.
Quando o volume de conteúdo estático é grande, o Vue 3 utiliza a função _createStaticVNode para gerar um nó estático no cliente, que é inserido via innerHTML. Isso elimina a necessidade de instanciar nós virtuais e aplicar diff sobre aquela parte da árvore.
Apêndice: PatchFlags
As flags indicam qual aspecto de um nó deve ser verificado durante o diff. Nós com HOISTED (-1) são completamente ignorados; BAIL (-2) força o diff a sair do modo otimizado.
export const enum PatchFlags {
TEXT = 1, // nó de texto dinâmico
CLASS = 1 << 1, // 2 - classe dinâmica
STYLE = 1 << 2, // 4 - estilo dinâmico
PROPS = 1 << 3, // 8 - atributos dinâmicos, exceto class/style
FULL_PROPS = 1 << 4, // 16 - chave dinâmica; diff completo necessário
HYDRATE_EVENTS = 1 << 5, // 32 - nó com eventos a serem hidratados
STABLE_FRAGMENT = 1 << 6, // 64 - fragmento com ordem filha estável
KEYED_FRAGMENT = 1 << 7, // 128 - fragmento com key
UNKEYED_FRAGMENT = 1 << 8, // 256 - fragmento sem key
NEED_PATCH = 1 << 9, // 512 - comparar apenas partes não-prop
DYNAMIC_SLOTS = 1 << 10, // 1024 - slots dinâmicos
HOISTED = -1, // nó estático, ignorado no diff
BAIL = -2 // desativa o modo otimizado
}