Vue.js: Reatividade em Profundidade

Última atualização: 2026-08-26

Compreender os princípios por trás da reatividade do Vue 3 permitirá que você escreva código mais eficiente e depure problemas complexos relacionados à reatividade. Esta lição aborda a implementação subjacente do sistema de reatividade do Vue 3: interceptadores de proxy, resolução de dependências, gatilhos de atualização e os mecanismos internos de refs, propriedades computadas e watches.

O Vue 3 substitui o Object.defineProperty do Vue 2 pelo Proxy do ES6, oferecendo suporte nativo a matrizes, Maps e Sets, além de proporcionar um aumento de desempenho de 2x. Depois de entender como ele funciona, você poderá evitar cinco armadilhas comuns relacionadas a dados reativos.

1. O que você vai aprender


2. Um incidente de “congelamento” envolvendo uma tabela com 100.000 linhas

(1) Problema: Uma lista de 100.000 itens faz com que a página congele após ser inserida em um ref

Alice carregou uma tabela de produtos com 10.000 linhas para o administrador:

JS
// ❌ The "Broken" Version:deep reactive Processing 10 Ten Thousand Lines
import { ref } from 'vue'

const products = ref([
  { id: 1, name: 'Product 1', price: 99, ... },
  // ... 10,000 items ...
])

// Vue For each property、All nested objects are created Proxy
// Memory Usage:100MB+
// First Render: 3s
// Scroll:10 FPS(Lag)

O monitor de desempenho exibe:

“10.000 linhas × 4 propriedades aninhadas × sobrecarga do proxy = 40.000 proxies. Cada acesso à propriedade aciona o rastreamento. Memória: 100 MB. FPS: 10.”

(2) Solução com shallowRef + markRaw no Vue

JS
import { shallowRef, markRaw } from 'vue'

// ✅ shallowRef:Follow Only .value Replace All
const products = shallowRef([
  { id: 1, name: 'Product 1' },
  // ... 10,000 items (Will not be Proxy-wrapped) ...
])

// ✅ markRaw:The tagged object never responds(Fastest)
const staticConfig = markRaw({
  apiUrl: 'https://api.example.com',
  version: '1.0'
})

// Replace All
products.value = await fetchProducts()  // Trigger only 1 Next Update

(3) Receita

Após a otimização:


3. Interceptação de proxy: a pedra angular da reatividade do Vue 3

(1) O que é um proxy?

Proxy é um wrapper de objeto introduzido no ES6 — ele pode interceptar 13 tipos de operações em objetos (get, set, has, deleteProperty, etc.).

JS
// Basics Proxy Example
const target = { name: 'Alice', age: 25 }

const proxy = new Proxy(target, {
  get(target, key) {
    console.log(`Read ${key}`)
    return Reflect.get(target, key)
  },
  set(target, key, value) {
    console.log(`Write ${key} = ${value}`)
    return Reflect.set(target, key, value)
  }
})

proxy.name  // Output "Read name",Back 'Alice'
proxy.age = 26  // Output "Write age = 26"

(2) Implementação reativa do Vue 3 (versão simplificada)

JS
// Vue 3 Inside reactivity A Simplified Implementation of a Package
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      // 1. Dependency Collection:Track who is using this property
      track(target, key)
      
      // 2. Recursive Wrapping:Nested objects also become reactive
      const value = Reflect.get(target, key, receiver)
      if (isObject(value)) {
        return reactive(value)
      }
      return value
    },
    set(target, key, value, receiver) {
      // 1. Setting Value
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      
      // 2. Trigger an update:Notify all locations where this property is used
      if (oldValue !== value) {
        trigger(target, key)
      }
      return result
    }
  })
}

(3) Proxy x Object.defineProperty

Aspecto Object.defineProperty (Vue 2) Proxy (Vue 3)
Suporte a matrizes Requer tratamento especial Suporte nativo
Mapa/Conjunto
Adicionar uma nova propriedade Requer Vue.set Auto-react
Desempenho Média Melhor (Lazy Proxy)
Compatibilidade com navegadores IE9+ Não compatível com o IE (requer um polyfill)

4. track + trigger: Coleta de dependências e acionamento de atualizações

(1) Fluxograma completo

100%
sequenceDiagram
    participant T as Template
    participant C as Component
    participant R as Reactive<br/>(Proxy)
    participant W as WeakMap<br/>(target → Map)
    participant D as Data
    
    T->>C: 1. Render (Read count)
    C->>R: 2. get count
    R->>W: 3. track(target, 'count')
    W->>W: 4. Record the current activeEffect
    R->>D: 5. Read 0
    R-->>C: 6. Return 0
    C-->>T: 7. Display "Stock: 0"
    
    Note over D,T: The user clicks the button
    
    D->>R: 8. set count = 1
    R->>W: 9. trigger(target, 'count')
    W->>W: 10. Find all dependencies
    R->>C: 11. Scheduling Update
    C-->>T: 12. Display "Stock: 1"

(2) Estrutura global de armazenamento de dependências

JS
// Vue 3 Internal Data Structures
const targetMap = new WeakMap()
// targetMap = { obj1: Map1, obj2: Map2, ... }
// Map1 = { key1: Set1, key2: Set2, ... }
// Set1 = Set<activeEffect1, activeEffect2, ...>

function track(target, key) {
  // 1. Get target Corresponding Map
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  
  // 2. Get key Corresponding Set
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  
  // 3. Record the currently active effect
  if (activeEffect) {
    dep.add(activeEffect)
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    // Notify all dependencies to re-execute
    dep.forEach(effect => effect.run())
  }
}

(3) 5 pontos-chave

Pontos-chave Descrição
rastrear ao obter Ler propriedade → Registrar “quem está usando”
Quando um gatilho é definido Alterar propriedade → Notificar “todos os usuários”
Armazenamento WeakMap Coleta automática de lixo (limpeza automática quando o objeto de destino é destruído)
activeEffect O efeito que está sendo executado no momento (render / watch / computed)
Processamento em lote Várias modificações acionam apenas uma atualização (nextTick)

5. A implementação interna do ref

(1) O código-fonte do ref (versão simplificada)

JS
// Vue 3 ref Internal Implementation
class RefImpl {
  constructor(value) {
    this._value = isObject(value) ? reactive(value) : value
  }
  
  get value() {
    // 1. track
    track(this, 'value')
    return this._value
  }
  
  set value(newValue) {
    // 1. Compare the old and new values
    if (hasChanged(this._value, newValue)) {
      this._value = isObject(newValue) ? reactive(newValue) : newValue
      // 2. trigger
      trigger(this, 'value')
    }
  }
}

function ref(value) {
  return new RefImpl(value)
}

(2) Como funciona a descompactação automática

VUE
<template>
  <!-- Automatic unpacking in templates:{{ count }} rather than {{ count.value }} -->
  <p>{{ count }}</p>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
console.log(count.value)  // 0(JS Domestic Demand .value)
console.log(count)        // 0(devtools Displayed in the middle 0)
</script>

Princípio: Durante a compilação do modelo, o compilador adiciona automaticamente .value a ref (não é necessário escrever isso manualmente).


6. A implementação interna do computed

(1) Sinalizador “preguiçoso + sujo”

JS
// Vue 3 computed Simplified Implementation
function computed(getter) {
  const ref = {
    // 1. Cache value
    _value: undefined,
    
    // 2. Has it expired?(Needs to be recalculated)
    _dirty: true,
    
    // 3. getter
    get value() {
      // Lazy Evaluation:Counted only on the first visit
      if (this._dirty) {
        this._value = effect(getter)  // Collect Dependencies
        this._dirty = false
      }
      
      // 4. track(Who is using me?)
      track(this, 'value')
      return this._value
    }
  }
  
  // 5. When dependencies change,Mark dirty
  effect(() => getter()).scheduled = () => {
    ref._dirty = true
    trigger(ref, 'value')  // For triggering computed Component Updates
  }
  
  return ref
}

(2) Mecanismo de armazenamento em cache

JS
const count = ref(0)
const double = computed(() => count.value * 2)

console.log(double.value)  // 0(First Calculation)
console.log(double.value)  // 0 (Cached, Do not recalculate)
count.value = 5
console.log(double.value)  // 10(Recalculation)

7. Otimização de desempenho: shallowRef / shallowReactive / markRaw

(1) shallowRef: Resposta superficial

JS
import { shallowRef, triggerRef } from 'vue'

const products = shallowRef([
  { id: 1, name: 'iPhone', price: 999 },
  { id: 2, name: 'MacBook', price: 2499 }
])

// ❌ Not responding(Edit .value Internal Properties)
products.value[0].name = 'iPad'
// Will not trigger an update(shallowRef Follow Only .value Replace All)

// ✅ Trigger an update(Replace All)
products.value = [...products.value, { id: 3, name: 'iPad' }]

// ✅ Force Trigger(Manually after making internal changes trigger)
products.value[0].name = 'iPad'
triggerRef(products)

(2) shallowReactive: Reatividade superficial

JS
import { shallowReactive } from 'vue'

const state = shallowReactive({
  user: { name: 'Alice' },
  settings: { theme: 'dark' }
})

// ✅ Response:Changes to Top-Level Properties
state.user = { name: 'Bob' }

// ❌ Not responding:Nested Properties
state.user.name = 'Charlie'

(3) markRaw: Nunca responde

JS
import { markRaw, reactive } from 'vue'

// Examples of Third-Party Libraries (e.g. ECharts, Mapbox) No need for reactive design
const map = markRaw(new Map())
const chart = markRaw(echarts.init(element))

const state = reactive({
  map,    // ✅ Will not be Proxy Packaging
  chart   // ✅ Performance Improvements 30%
})

(4) 5 principais casos de uso

Cenário Solução Melhoria no desempenho
Lista de Big Data (mais de 100.000) shallowRef 50x
Exemplos de bibliotecas de terceiros markRaw 30%
Criação/destruição frequente de objetos shallowReactive 20%
Configuração somente leitura markRaw 10%
Dados imutáveis shallowRef 5%

8. Exemplo completo: aplicação dos princípios do design responsivo

▶ Exemplo: 1. Versão simplificada de “track + trigger”

JS 📖 Somente leitura
// Simulation Vue 3 Responsive Systems
const targetMap = new WeakMap()
let activeEffect = null

function track(target, key) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  if (activeEffect) {
    dep.add(activeEffect)
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) dep.forEach(effect => effect())
}

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      const result = Reflect.set(target, key, value)
      trigger(target, key)
      return result
    }
  })
}

// Usage
const state = reactive({ count: 0 })
let render = null

// Simulation render Function
activeEffect = () => console.log(`Render: count = ${state.count}`)
render = activeEffect
render()  // Output "Render: count = 0"

state.count = 1  // Trigger render,Output "Render: count = 1"
42 linhas de lógica (limite de 40, somente leitura)

▶ Exemplo: 2. Comparação das 5 principais métricas de desempenho

Modo Desempenho em 100.000 linhas Memória Aplicável
ref([]) 100 ms 100 MB Dados de pequeno porte
reactive({...}) 100 ms 100 MB Objetos aninhados
shallowRef([]) 5 ms 5 MB Lista extensa
shallowReactive({...}) 30 ms 30 MB Objeto superficial
markRaw({...}) 2 ms 2 MB Instância de terceiros

▶ Exemplo: 3. Estrutura interna de ref + reactive

JS
import { ref, reactive, shallowRef, markRaw } from 'vue'

// ref:Wrap any value
const count = ref(0)
console.log(count)  // RefImpl { _value: 0, __v_isRef: true, ... }

// reactive:Items to Be Packaged
const state = reactive({ count: 0 })
console.log(state)  // Proxy { count: 0, ... }

// shallowRef: Shallow
const list = shallowRef([1, 2, 3])
console.log(list)  // RefImpl { _value: [1, 2, 3] }  // Arrays are not reactive Packaging

// markRaw:Does not respond at all
const map = markRaw(new Map())
console.log(map)  // Map { ... }  // Regular Map,No Proxy
▶ Experimente

▶ Exemplo: 4. Cinco causas fundamentais comuns de fracasso

Cenário de falha Causa principal Solução
Substitua o objeto reativo inteiro A referência reativa foi alterada Envolva-o em uma ref ou modifique suas propriedades
Desconstruir a resposta ausente Desconstruir o valor original Usar toRefs
Mutação em array com shallowRef não dispara shallowRef só rastreia substituição de .value Substitua o array ou use triggerRef
Atribuição assíncrona não está respondendo Função assíncrona fora do caminho Envolvendo com ref/reactive
As propriedades de objetos aninhados não são atualizadas Corrigido no Vue 3 (o Vue 2 requer o uso de Vue.set) Atualizações automáticas

▶ Exemplo: 5. Teste de desempenho de proxy

JS
// Performance Testing:10,000 A property
const obj = {}
for (let i = 0; i < 10000; i++) {
  obj[`key${i}`] = i
}

console.time('Object.defineProperty')
// ... 10,000 getter/setter
console.timeEnd('Object.defineProperty')
// Usually 50-100ms

console.time('Proxy')
const proxy = new Proxy(obj, { /* ... */ })
console.timeEnd('Proxy')
// Usually 1-5ms(Create only when accessed)
▶ Experimente

▶ Exemplo: 6. 5 dicas importantes para depuração

JS
// 1. watchEffect Printing Dependencies
watchEffect(() => {
  console.log('Dependency:', { a: state.a, b: state.b })
})

// 2. Trigger a manual update(forceUpdate)
import { triggerRef } from 'vue'
triggerRef(stateRef)

// 3. Check the responsive status
import { isReactive, isRef, isReadonly, isProxy } from 'vue'
isReactive(obj)  // true/false
isRef(refObj)    // true/false

// 4. Original Object(Remove Proxy)
import { toRaw } from 'vue'
const raw = toRaw(reactiveObj)

// 5. Performance Labels
import { markRaw } from 'vue'
const fast = markRaw(expensiveObj)
▶ Experimente

❓ Perguntas Frequentes

P: Por que o Vue 3 usa o Proxy em vez do Object.defineProperty? R: O Proxy oferece suporte nativo a matrizes, Maps e Sets, pode interceptar mais operações (13 contra 2), responde automaticamente à adição de novas propriedades e oferece melhor desempenho (proxy preguiçoso). A única desvantagem é que ele não é compatível com o IE11.

P: Onde track e trigger são chamados? R: track é chamado no método get de um objeto reativo (para registrar as dependências quando get é chamado). trigger é chamado no método set (para notificar todas as dependências quando set é chamado).

P: Qual é a diferença entre shallowRef e ref? R: ref é uma resposta profunda (os objetos aninhados também são proxies). shallowRef rastreia apenas a substituição de todo o .value e não lida com propriedades internas. É adequado para listas grandes (mais de 100.000 linhas).

P: Como o computed sabe quando uma dependência foi alterada? R: O computed envolve internamente o getter em um effect. Quando os dados reativos usados no getter mudam, o effect é acionado, redefinindo o sinalizador dirty para que o valor seja recalculado na próxima vez em que for acessado.

P: Qual é a diferença na implementação interna entre watch e watchEffect? R: watch especifica explicitamente uma fonte (dependência), e a função de retorno recebe tanto newVal quanto oldVal. watchEffect não especifica uma fonte; ele rastreia automaticamente as dependências dentro do corpo da função, e a função de retorno recebe apenas o novo valor.

P: Como acionar uma atualização ao modificar propriedades internas com shallowRef? R: Acione-a manualmente usando triggerRef(shallowRef). Cenário: Forçar uma atualização da visualização após a modificação em lote de propriedades internas.

P: O que um proxy encapsulado por reactive exibe no console? R: Ele exibe Proxy { ... } wrapper object; the properties of the original object are not visible. Use toRaw(proxyObj) para recuperar o objeto original.


📖 Resumo


📝 Exercícios

  1. Questões básicas (Dificuldade: ⭐)

    Implemente uma versão simplificada de um sistema reativo (baseado em Proxy):

    • Uma função reativa de 5 linhas
    • 5 linhas de rastreamento + gatilho
    • Demonstração: definir 100 vezes → acionar o efeito 1 vez (processamento em lote)
  2. Problemas avançados (Dificuldade: ⭐⭐)

    Otimização de listas grandes usando shallowRef e markRaw:

    • Uma lista de 1.000 produtos (usando shallowRef)
    • Inclui instâncias de bibliotecas de terceiros (usando markRaw)
    • Medir o uso de memória e as melhorias na taxa de quadros por segundo (FPS)
  3. Problema de desafio (Dificuldade: ⭐⭐⭐)

    Implementar um “rastreador de dependências” completo:

    1. O watchEffect imprime todas as dependências
    2. triggerRef: Acionar manualmente uma atualização
    3. Demonstração das funções utilitárias toRaw, isReactive e isRef
    4. Análise da causa raiz de 5 cenários de falha (substituição de referência / desconstrução / matrizes / assíncrono / aninhamento)
    5. Testes de desempenho: comparação de memória entre ref, shallowRef e markRaw
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%