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