Fundamentos da Reatividade
PREFERÊNCIA DE API
Esta páginas e muitos outros capítulos adiante neste guia contém diferente conteúdo para a API de Opções e API de Composição. A tua preferência atual é a API de Composição. Tu podes alternar entre os estilos de API utilizando o interruptor "Preferência de API" no canto superior esquerdo da barra lateral.
Declarando Estado Reativo
ref()
Na API de Composição, a maneira recomendada de declarar estado reativo é usando a função ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
recebe o argumento e retorna-o embrulhado dentro dum objeto de referência com uma propriedade .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Consulte também: Tipos para as Referências
Para acessar as referências no modelo de marcação dum componente, declaramos e as retornamos a partir da função setup()
dum componente:
js
import { ref } from 'vue'
export default {
// `setup()` é um gatilho especial dedicado para a API de Composição.
setup() {
const count = ref(0)
// expor a referência ao modelo de marcação
return {
count
}
}
}
template
<div>{{ count }}</div>
Repara que não precisávamos anexar .value
quando usamos a referência no modelo de marcação. Por conveniência, as referências são automaticamente desembrulhada quando usadas dentro dos modelos de marcação (com algumas advertências).
Tu também podes modificar a referência diretamente nos manipuladores de evento:
template
<button @click="count++">
{{ count }}
</button>
Para lógica mais complexa, podemos declarar funções que modificam as referências no mesmo escopo e expo-las como métodos ao lado do estado:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// `.value` é necessário na JavaScript
count.value++
}
// também não esqueças de expor a função.
return {
count,
increment
}
}
}
Os métodos expostos podem então ser usados como manipuladores de evento:
template
<button @click="increment">
{{ count }}
</button>
Nesta ligação está o exemplo ao vivo na Codepen, sem usar quaisquer ferramentas de construção.
<script setup>
Expor manualmente o estado e os métodos através de setup()
pode ser verboso. Felizmente, pode ser evitado quando usamos Componentes de Ficheiro Único. Nós podemos simplificar o uso com o <script setup>
:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
Experimentar na Zona de Testes
As importações, variáveis, e funções de alto nível declaradas no <script setup>
são automaticamente usáveis no modelo de marcação do mesmo componente. Pense do modelo de marcação como uma função de JavaScript declarada no mesmo escopo - naturalmente tem acesso à tudo for declarado ao lado dele.
DICA
Para o resto do guia, estaremos primariamente a usar a sintaxe de Componente de Ficheiro Único + <script setup>
para os exemplos de código da API de Composição, uma vez que é uso mais comum para os programadores de Vue.
Se não estiveres a usar Componente de Ficheiro Único, ainda podes usar a API de Composição com a opção setup()
.
Porquê Referências?
Tu podes estar a perguntar a si mesmo porquê precisamos de referências com .value
no lugar de variáveis simples. Para explicar isto, precisaremos de discutir brevemente como o sistema de reatividade da Vue funciona.
Quando usas uma referência no modelo de marcação, e mudas o valor da referência mais tarde, a Vue deteta automaticamente a mudança e atualiza o DOM por consequência. Isto é tornado possível com um rastreio de dependência baseado no sistema de reatividade. Quando um componente é desenhado pela primeira vez, a Vue rastreia todas as referências que foram usadas durante o desenho. Depois, quando uma referência for mudada, acionará o redesenho para os componentes que estão a rastreia-lo.
Na JavaScript padrão, não existe nenhuma maneira de detetar o acesso ou mutação de variáveis simples. Mas podemos intercetar operações de recuperação e definição duma propriedade.
A propriedade .value
dá a Vue oportunidade de detetar quando uma referência foi acessada ou mudada. Nos bastidores, a Vue realiza o rastreio no seu recuperador, e realiza acionamento no seu definidor. Concetualmente, podes pensar duma referência como um objeto que se parece com isto:
js
// pseudo-código, e não implementação verdadeira
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Uma outra característica fantástica das referências é que ao contrário das variáveis simples, podes passar referências para funções enquanto reténs o acesso ao último valor e a conexão da reatividade. Isto é particularmente útil quando refazemos lógica complexa em código reutilizável.
O sistema de reatividade é discutido em mais detalhes na seção Reatividade em Profundidade
Reatividade Profunda
Na Vue, o estado é profundamente reativo por padrão. Isto significa que podes esperar que as mudanças serem detetadas mesmo quando alteras objetos ou arranjos encaixados:
As referências podem segurar qualquer tipo de valor, incluindo objetos profundamente encaixados, vetores, ou estruturas de dados embutidas da JavaScript tal como a Map
.
Uma referência tornará o seu valor profundamente reativo, Isto significa que podes esperar as mudanças serem detetadas mesmo quando mudares os objetos ou vetores encaixados:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// estes funcionarão como esperado.
obj.value.nested.count++
obj.value.arr.push('baz')
}
Os valores não primitivos são transformados em delegações reativas através da reactive()
, que é discutida abaixo.
Também é possível abandonar a reatividade profunda com as referências superficiais. Para as referências superficiais, apenas o acesso de .value
é rastreado para reatividade. As referências superficiais pode ser usadas para otimização do desempenho evitando o custo de observação dos grandes objetos, ou em casos onde o estado interno é gerido por uma biblioteca externa.
Leitura avançada:
Tempo de Atualização do DOM
Quando alteras o estado reativo, o DOM é atualizado automaticamente. No entanto, deve ser notado que as atualizações do DOM não são aplicadas de maneira síncrona. Ao invés disto, a Vue ampara-os até o "próximo momento" no ciclo de atualização para garantir que cada componente precise atualizar apenas uma vez não importa quantas mudanças de estado tens feito.
Para esperar pela atualização do DOM terminar depois de uma mudança de estado, podes utilizar a API global nextTick()
:
js
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// acessa o DOM atualizado
})
}
reactive()
Existe uma outra maneira de declarar o estado reativo, com a API reactive()
. Ao contrário duma referência que envolve o valor interno num objeto especial, a reactive()
torna um objeto por si só reativo:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Consulte também: Tipos para a Função
reactive
Uso no modelo de marcação:
template
<button @click="state.count++">
{{ state.count }}
</button>
Os objetos reativos são Delegações de JavaScript e comportam-se como objetos normais. A diferença é que a Vue é capaz de intercetar o acesso e mutação de todas as propriedades dum objeto reativo para rastreio e acionamento da reatividade.
reactive()
converte o objeto profundamente: os objetos encaixados também são envolvidos com reactive()
quando acessados. Também é chamada por ref()
internamente quando o valor da referência for um objeto. Semelhante às referências superficiais, existe também a API shallowReactive()
para abandonar a reatividade profunda.
Delegação Reativa vs. Original
É importante notar que o valor retornado da reactive()
é uma Delegação do objeto original, a qual não é igual ao objeto original:
js
const raw = {}
const proxy = reactive(raw)
// a delegação NÃO é igual ao original.
console.log(proxy === raw) // false
Apenas a delegação é reativa - a mutação do objeto original não acionará atualizações. Portanto, a boa prática quando estiveres trabalhando com o sistema de reatividade da Vue é utilizar exclusivamente as versões delegadas do teu estado.
Para garantir o acesso consistente à delegação, a chamada de reactive()
sobre o mesmo objeto sempre retorna a mesma delegação, e a chamada reactive()
sobre uma delegação existente também retorna aquela mesma delegação:
js
// a chamada de reactive() sobre o mesmo objeto retorna a mesma delegação
console.log(reactive(raw) === proxy) // true
// a chamada de reactive() sobre uma delegação retorna a si mesma
console.log(reactive(proxy) === proxy) // true
Este regra também aplica-se aos objetos encaixados. Devido a reatividade profunda, os objetos encaixados dentro dum objeto reativo também são delegações:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Limitações da reactive()
A API de reactive()
tem duas limitações
Tipos de valor limitados: apenas funciona para os tipos de objetos (objetos, arranjos, e tipos de coleção tais como
Map
eSet
). Ela não pode segurar tipos primitivos tais comostring
,number
, ouboolean
.Não substituir um objeto inteiro: uma vez que o rastreio da reatividade da Vue funciona sobre o acesso de propriedade, devemos sempre manter a mesma referência para objeto reativo. Isto significa que não podemos "substituir" facilmente um objeto reativo porque a conexão da reatividade para a primeira referência é perdida:
jslet state = reactive({ count: 0 }) // a referência acima ({ count: 0 }) não está mais a ser rastreada // (conexão da reatividade está perdida!) state = reactive({ count: 1 })
Não é amigável à desestruturação: quando desestruturamos uma propriedade dum objeto reativo em variáveis locais, ou quando passamos esta propriedade à uma função, perderemos a conexão da reatividade:
jsconst state = reactive({ count: 0 }) // `count` está desconectado da `state.count` quando desestruturada. let { count } = state.count // não afeta o estado original count++ // a função recebe um número simples e // não será capaz de rastrear as mudanças para `state.count` // temos de passar o objeto inteiro para reter a reatividade callSomeFunction(state.count)
Devido à estas limitações, recomendamos usar ref()
como API primária para declaração de estado reativo.
Detalhes Adicionais do Desembrulhamento da Referência
Como Propriedade de Objeto Reativo
Uma referência é automaticamente desembrulhada quando acessada ou modificada como uma propriedade dum objeto reativo. Em outras palavras, comporta-se como uma propriedade normal:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Se uma nova referência for atribuída à uma propriedade ligada à uma referência existente, substituirá a referência antiga:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// a referência original agora está desconectada da `state.count`
console.log(count.value)
O desembrulhamento da referência apenas acontece quando encaixada dentro dum objeto reativo profundo. Não aplica-se quando é acessada como uma propriedade dum objeto de reatividade superficial.
Advertências nos Vetores e Coleções
Ao contrário dos objetos reativos, não existe desembrulhamento realizado quando a referência é acessada como um elemento dum vetor ou um tipo de coleção nativa como Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// neste caso precisas de `.value`
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// neste caso precisas de `.value`
console.log(map.get('count').value)
Advertências Quando Desembrulhamos nos Modelos de Marcação
O desembrulhamento de referência nos modelos de marcação apenas aplica-se se a referência for uma propriedade de alto nível no contexto de interpretação do modelo de marcação.
No exemplo abaixo, count
e object
são propriedades de alto nível, mas object.id
não:
js
const count = ref(0)
const object = { id: ref(0) }
Portanto, esta expressão funciona como esperado:
template
{{ count </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }}
...enquanto isto NÃO:
template
{{ object.id </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }}
O resultado desenhado será [object Object]1
uma vez que object.id
não é desembrulhado quando avaliamos a expressão e continua um objeto de referência. Para corrigir isto, podemos desestruturar id
à uma propriedade de alto nível:
js
const { id } = object
template
{{ id </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">+</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }}
Agora o resultado da interpretação será 2
.
Um outra coisa à notar é que uma referência é desembrulhada se for o valor avaliado final duma interpolação de texto (por exemplo, um marcador {{ }}
, então o seguinte exemplo desenhará 1
):
template
{{ object.id }}
Isto é apenas um funcionalidade de conveniência da interpolação de texto e é equivalente ao {{ object.id.value }}
.