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
- Como um proxy intercepta operações get/set
- O papel da API Reflect na programação reativa
- Processo principal: rastreamento (coleta de dependências) + gatilho (gatilho para uma atualização)
- A implementação interna de
ref,reactiveecomputed - Diferenças internas entre
watchewatchEffect - Otimização de desempenho: shallowRef / markRaw
- 5 causas fundamentais comuns de falhas no design responsivo
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:
// ❌ 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
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:
- Número de proxies: 40.000 → 0 (o shallowRef não cria proxies)
- Uso de memória: 100 MB → 5 MB
- Primeira renderização: 3 s → 200 ms
- FPS: 10 → 60 (fluido)
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.).
// 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)
// 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
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
// 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)
// 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
<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”
// 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
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
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
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
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”
// 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"
▶ 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
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
▶ 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
// 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)
▶ Exemplo: 6. 5 dicas importantes para depuração
// 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)
❓ 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
tracketriggersão chamados? R:tracké chamado no métodogetde um objeto reativo (para registrar as dependências quandogeté chamado).triggeré chamado no métodoset(para notificar todas as dependências quandoseté chamado).
P: Qual é a diferença entre
shallowReferef? R:refé uma resposta profunda (os objetos aninhados também são proxies).shallowRefrastreia apenas a substituição de todo o.valuee não lida com propriedades internas. É adequado para listas grandes (mais de 100.000 linhas).
P: Como o
computedsabe quando uma dependência foi alterada? R: Ocomputedenvolve internamente o getter em umeffect. Quando os dados reativos usados no getter mudam, oeffecté acionado, redefinindo o sinalizadordirtypara que o valor seja recalculado na próxima vez em que for acessado.
P: Qual é a diferença na implementação interna entre
watchewatchEffect? R:watchespecifica explicitamente uma fonte (dependência), e a função de retorno recebe tantonewValquantooldVal.watchEffectnã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 usandotriggerRef(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
reactiveexibe no console? R: Ele exibeProxy { ... }wrapper object; the properties of the original object are not visible. UsetoRaw(proxyObj)para recuperar o objeto original.
📖 Resumo
- O núcleo reativo do Vue 3: Interceptação por proxy + track (coleta de dependências) + trigger (acionamento de atualizações)
- O Proxy é mais poderoso do que o
Object.defineProperty: ele oferece suporte nativo a matrizes, Maps e Sets, além de responder automaticamente a novas propriedades. - Referência interna: classe RefImpl; rastreamento/acionamento durante operações de leitura/gravação
- No interior do
computed: sinalizador “dirty” + avaliação preguiçosa + armazenamento em cache - 5 otimizações de desempenho: shallowRef / shallowReactive / markRaw
- 5 causas comuns de falhas no Responsive: Substituição / Desestruturação / Matrizes / Operações assíncronas / Aninhamento
📝 Exercícios
-
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)
-
Problemas avançados (Dificuldade: ⭐⭐)
Otimização de listas grandes usando
shallowRefemarkRaw:- 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)
-
Problema de desafio (Dificuldade: ⭐⭐⭐)
Implementar um “rastreador de dependências” completo:
- O
watchEffectimprime todas as dependências - triggerRef: Acionar manualmente uma atualização
- Demonstração das funções utilitárias toRaw, isReactive e isRef
- Análise da causa raiz de 5 cenários de falha (substituição de referência / desconstrução / matrizes / assíncrono / aninhamento)
- Testes de desempenho: comparação de memória entre ref, shallowRef e markRaw
- O