Otimizações de Performance no Vue 3

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 */)
  ]))
}

  1. 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 */)
  ]))
}

  1. 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.

  1. 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
}

Tags: Vue 3 PatchFlag Virtual DOM SSR HoistStatic

Publicado em 6-20 05:15