Skip to content

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 OpçõesAPI 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

Com a API de Opções, usamos a opção data para declarar o estado reativo dum componente. O valor da opção deve ser uma função que retorna um objeto. A Vue chamará a função quando criarmos uma nova instância de componente, e embrulha o objeto retornado no seu sistema de reatividade. Quaisquer propriedades de alto nível deste objeto são delegadas sobre a instância do componente (this nos métodos e funções gatilhos do ciclo de vida):

js
export default {
  data() {
    return {
      count: 1
    }
  },

  // `mounted` é uma função gatilho do
  // ciclo de vida que explicaremos depois
  mounted() {
    // `this` refere-se a instância do componente.
    console.log(this.count) // => 1

    // o dado também pode ser alterado
    this.count = 2
  }
}

Experimentar na Zona de Testes

Estas propriedades da instância apenas são adicionadas depois da instância ser criada, então precisamos garantir que estas estão todas presentes no objeto retornado pela função data. Onde necessário, usamos null, undefined ou outros valores marcadores de posição para as propriedades onde o valor desejado ainda não estiver disponível.

É possível adicionar uma nova propriedade diretamente ao this sem a incluir na data. No entanto, as propriedades adicionadas desta maneira não serão capazes de acionar as atualizações reativas.

A Vue usa um prefixo $ quando expõe suas próprias APIs embutidas através da instância do componente. Esta também reserva o prefixo _ para as propriedades internas. Nós devemos evitar usar nomes para as propriedades data de alto nível que começam com qualquer um destes caracteres.

Delegação Reativa vs Original

Na Vue 3, os dados são tornados reativos influenciando as Delegações da JavaScript. Os utilizadores vindo da Vue 2 devem estar cientes do seguinte caso extremo:

js
export default {
  data() {
    return {
      someObject: {}
    }
  },
  mounted() {
    const newObject = {}
    this.someObject = newObject

    console.log(newObject === this.someObject) // false
  }
}

Quando acessamos this.someObject depois de atribuí-lo, o valor é uma delegação reativa do newObject original. Diferente da Vue 2, o newObject original é deixado intacto e não será tornado reativo: devemos certificar-nos de acessar sempre o estado reativo como uma propriedade do this.

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.

Declarando Métodos

Para adicionarmos métodos à instância dum componente usamos a opção methods. Esta deve ser um objeto contendo os métodos desejados:

js
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    // os métodos podem ser chamados nas
    // funções gatilhos do ciclo de vida, ou noutros métodos!
    this.increment()
  }
}

A Vue vincula automaticamente o valor de this dos methods para que refira-se sempre à instância do componente. Isto garante que um método retém o valor de this correto se for usado como um ouvinte de evento ou função de resposta. Nós devemos evitar usar funções de flecha quando definimos methods, uma vez que estas impedem a Vue de vincular o valor de this apropriado:

js
export default {
  methods: {
    increment: () => {
      // MAU: nenhum acesso ao `this` nesta declaração!
    }
  }
}

Tal como todas as outras propriedades da instância do componente, as propriedades de methods são acessíveis a partir de dentro do modelo de marcação do componente. Dentro dum modelo de marcação são comummente usadas como ouvintes de evento:

template
<button @click="increment">{{ count }}</button>

Experimentar na Zona de Testes

No exemplo acima, o método increment será chamado quando o <button> for clicado.

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:

js
export default {
  data() {
    return {
      obj: {
        nested: { count: 0 },
        arr: ['foo', 'bar']
      }
    }
  },
  methods: {
    mutateDeeply() {
      // estas funcionarão como o esperado.
      this.obj.nested.count++
      this.obj.arr.push('baz')
    }
  }
}

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
  })
}
js
import { nextTick } from 'vue'

export default {
  methods: {
    async increment() {
      this.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:

  1. Tipos de valores limitados: apenas funciona para os tipos de objetos (objetos, vetores, e tipos de coleção tais como Map e Set). Esta não pode segurar tipos primitivos tais como string, number, ou boolean.

  2. 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:

    js
    let 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 })
  3. 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:

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

Métodos com Estado

Em alguns casos, podemos precisar criar dinamicamente uma função de método, por exemplo, criando um manipulador de evento de chamada reduzida:

js
import { debounce } from 'lodash-es'

export default {
  methods: {
    // Reduzir chamadas com a Lodash
    click: debounce(function () {
     // ... responde ao clique ...
    }, 500)
  }
}

No entanto, esta abordagem é problemática porque os componentes que são reutilizados porque uma função de chamada reduzida tem estado: esta mantém algum estado interno sobre o tempo decorrido. Se várias instância do componente partilharem a mesma função de chamada reduzida, interferirão umas com as outras.

Para manter a função de chamada reduzida de cada instância do componente independente das outras, podemos criar uma versão de chamada reduzida na função gatilho do ciclo de vida created:

js
export default {
  created() {
    // agora cada instância tem sua própria cópia do
    // manipulador de chamada reduzida
    this.debouncedClick = _.debounce(this.click, 500)
  },
  unmounted() {
    // também é uma boa ideia cancelar o temporizador
    // quando o componente for removido
    this.debouncedClick.cancel()
  },
  methods: {
    click() {
     // ... responder ao clique ...
    }
  }
}
Fundamentos da Reatividade has loaded