Fundamentos da Reatividade
PREFERÊNCIA DE API
Esta página e muitos outros capítulos adiante neste guia contém conteúdo diferente para API de Opções e a API de Composição. Nossa preferência atual é a API de Composição. Nós podemos alternar entre os estilos de API usando os interruptores de "Preferência de API" acima da barra lateral esquerda.
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)
A 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: Tipificando as Referências
Para acessarmos as referências no modelo de marcação dum componente, as declaramos e as retornamos a partir da função setup()
dum componente:
js
import { ref } from 'vue'
export default {
// `setup()` é uma função gatilho especial
// dedicada 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 precisamos anexar .value
quando usamos a referência no modelo de marcação. Por conveniência, as referências são desembrulhadas automaticamente quando usadas dentro dos modelos de marcação (com algumas advertências).
Nós também podemos modificar a referência diretamente nos manipuladores de evento:
template
<button @click="count++">
{{ count }}
</button>
Para lógica mais complexa, podemos declarar função que alteram as referências no mesmo âmbito de aplicação e as expor 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++
}
// não esqueçamos também 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>
Eis o exemplo ao vivo na Codepen, sem usar quaisquer ferramentas de construção.
<script setup>
**
Pode ser verboso expor manualmente o estado e os métodos através da setup()
. Felizmente, isto 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 no modelo de marcação como uma função de JavaScript declarada no mesmo âmbito de aplicação - esta naturalmente tem acesso a tudo que foi declarado ao seu lado.
NOTA
Para o resto do guia, usaremos primariamente a sintaxe de Componente de Ficheiro Único + <script setup>
para os exemplos de código da API de Composição, uma vez que é o uso mais comum para os programadores de Vue.
Se não estivermos usando o Componente de Ficheiro Único, ainda podemos usar a API de Composição com a opção setup()
.
Por que Referências? **
Nós podemos estar a perguntar-nos por que razão precisamos de referências com .value
ao invés de variáveis simples. Para explicarmos isto, precisaremos discutir brevemente como o sistema de reatividade da Vue funciona.
Quando usamos uma referência num modelo de marcação, e mudamos o valor da referência em seguida, 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 toda referência que foi usada durante a interpretação. Mais tarde, quando uma referência for alterada, esta acionará uma reinterpretação dos componentes que a estiverem rastreando.
Na JavaScript padrão, não existe nenhuma maneira de detetar o acesso ou mutação das variáveis simples. No entanto, podemos intercetar as opções de recuperação e definição duma propriedade.
A propriedade .value
dá à Vue a oportunidade de detetar quando uma referência foi acessada ou alterada. Nos bastidores, a Vue realiza o rastreio no seu recuperador, e realiza acionamento no seu definidor. Concetualmente, podemos pensar numa referência como um objeto que parece-se 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 é a que diferente das variáveis simples, podemos passar as referências às funções enquanto retemos o acesso ao valor mais recente e a conexão da reatividade. Isto é especialmente útil quando refazemos uma 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 podemos esperar as mudanças serem detetadas mesmo quando alteramos os objetos e vetores encaixados:
As referências podem segurar qualquer tipo de valor, incluindo objetos profundamente encaixados, vetores, ou estruturas de dados embutidas da JavaScript como a Map
.
Uma referência tornará o seu valor profundamente reativo. Isto significa que podemos esperar as mudanças serem detetadas mesmo quando mudarmos os objetos ou vetores encaixados:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// estas 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 podem 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 alteramos 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 amortece-as até o "próximo tiquetaque" no ciclo de atualização para garantir que cada componente atualize-se apenas uma vez não importa quantas mudanças de estado fizemos.
Para aguardar-mos a atualização do DOM terminar depois duma mudança de estado, podemos usar a API global nextTick()
:
js
import { nextTick } from 'vue'
async function increment() {
state.count++
await nextTick(() => {
// Agora o DOM está atualizado
})
}
reactive()
**
Existe uma outra maneira de declarar o estado reativo, com a API reactive()
. Ao contrário duma referência que embrulha o valor interno num objeto especial, a reactive()
torna um objeto reativo:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Consulte também: Tipificando 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 embrulhados com a reactive()
quando acessados. Esta também é chamada pela 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 a partir 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 - alterar o objeto original não acionará atualizações. Portanto, a boa prática quando trabalhamos com o sistema de reatividade da Vue é usar exclusivamente as versões delegadas do nosso estado.
Para garantir o acesso consistente à delegação, chamar reactive()
sobre o mesmo objeto sempre retorna a mesma delegação, e chamar reactive()
sobre uma delegação existente também retorna a mesma delegação:
js
// chamar `reactive()` sobre o mesmo objeto
// retorna a mesma delegação
console.log(reactive(raw) === proxy) // true
// chamar `reactive()` sobre uma delegação
// retorna a si mesma
console.log(reactive(proxy) === proxy) // true
Este regra também aplica-se aos objetos encaixados. Por causa da 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 algumas limitações:
Tipos de valores limitados: apenas funciona para os tipos de objetos (objetos, vetores, e tipos de coleção tais como
Map
eSet
). Esta não pode segurar tipos primitivos tais comostring
,number
, ouboolean
.Não é possível substituir um objeto inteiro: uma vez que o rastreio da reatividade da Vue funciona sobre o acesso de propriedade, sempre devemos manter a mesma referência ao objeto reativo. Isto significa que não podemos "substituir" facilmente um objeto reativo porque a conexão da reatividade à primeira referência é perdida:
jslet state = reactive({ count: 0 }) // a referência acima ({ count: 0 }) já não está sendo rastreada // (conexão da reatividade está perdida!) state = reactive({ count: 1 })
Não é amigável à desestruturação: quando desestruturamos a propriedade de tipo primitivo 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` é desconectada de `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 manter a reatividade callSomeFunction(state.count)
Por causa destas limitações, recomendados usar ref()
como API primária para declarar o estado reativo.
Detalhes Adicionais do Desembrulho de Referência **
Como Propriedade de Objeto Reativo **
Uma referência é automaticamente desembrulhada quando acessada ou alterada 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, esta 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 desembrulho de referência apenas acontece quando encaixada dentro dum objeto reativo profundo. Não se aplica quando é acessada como uma propriedade dum objeto reativo superficial.
Advertências nos Vetores e Coleções **
Ao contrário dos objetos reativos, não é efetuado nenhum desembrulho quando a referência é acessada como um elemento dum vetor reativo ou um tipo de coleção nativa como Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// neste caso precisamos de `.value`
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// neste caso precisamos de `.value`
console.log(map.get('count').value)
Advertências Quando Desembrulhamos nos Modelos de Marcação **
O desembrulho 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 + 1 }}
...enquanto esta NÃO:
template
{{ object.id + 1 }}
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 + 1 }}
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 }}
.