Clique aqui para a documentação da v3.x.

Você está navegando a documentação da v2.x e anterior. Para a v3.x, clique aqui.

Dados Computados e Observadores

Dados Computados

Expressões dentro de templates são muito convenientes, mas são destinadas a operações simples. Colocar muita lógica neles pode fazer com que fiquem inchados e que a sua manutenção fique mais complicada. Por exemplo:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

Neste ponto, o template não é mais tão simples e declarativo. Você tem que olhá-lo por alguns segundos antes de entender que ele exibe o valor de message na ordem reversa. O problema é agravado quando se deseja incluir uma mensagem na ordem reversa em mais algum lugar do template, gerando-se repetições de código.

Por isso que, para qualquer lógica mais complexa, usamos dados computados (computed properties no inglês, traduzidos como “dados” pois, durante a utilização em templates, se parecem efetivamente com propriedades definidas em data).

Exemplo Básico

<div id="example">
  <p>Mensagem original: "{{ message }}"</p>
  <p>Mensagem ao contrário: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Olá Vue'
  },
  computed: {
    // uma função "getter" computada (computed getter)
    reversedMessage: function () {
      // `this` aponta para a instância Vue da variável `vm`
      return this.message.split('').reverse().join('')
    }
  }
})

Resultado:

Mensagem original: "{{ message }}"

Mensagem ao contrário: "{{ reversedMessage }}"

Aqui nós declaramos um dado computado denominado reversedMessage. A função que fornecemos será usada como uma função getter para a propriedade vm.reversedMessage.

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

Pode abrir o console e testar o exemplo você mesmo. O valor de vm.reversedMessage sempre dependerá do valor em vm.message.

É possível vincular os dados computados em templates como se fossem dados comuns. Vue sabe que vm.reversedMessage depende de vm.message, então ele irá atualizar qualquer ligação que dependa de vm.reversedMessage sempre que vm.message for alterado. E o melhor é que criamos tal relação de dependência de forma declarativa: a função getter computada não tem efeitos colaterais, o que a torna fácil de testar e de compreender.

Cache de computed vs. Métodos

Você pode ter notado que é possível obter o mesmo resultado chamando um método:

<p>Mensagem ao contrário: "{{ reverseMessage() }}"</p>
// no componente
methods: {
  reverseMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

Em vez de um dado computado, podemos definir a mesma funcionalidade como um método. Como resultado final, ambas abordagens têm o mesmo efeito. No entanto, a diferença é que dados computados são cacheados de acordo com suas dependências reativas. Um dado computado somente será reavaliado quando alguma de suas dependências for alterada. Isso significa que enquanto message não sofrer alterações, múltiplos acessos ao reversedMessage retornarão o último valor calculado sem precisar executar a função novamente.

Isto inclusive significa que o seguinte dado computado nunca se alterará, pois Date.now() não é uma dependência reativa por natureza:

computed: {
  now: function () {
    return Date.now()
  }
}

Em comparação, a invocação de um método sempre irá rodar a função, toda vez que uma nova renderização ocorrer.

Por que realmente precisamos de cache? Imagine que temos um dado computado A muito pesado ao ser processado, exigindo um laço por um Array enorme e fazendo diversos cálculos a cada iteração. Aí temos outros dados computados que dependem de A para processar. Sem cache, nós acabaríamos executando o getter de A muito mais vezes do que o necessário! Em casos em que você realmente não deseje cache, simplesmente use um método no lugar.

Dados Computados vs. Observadores

Vue oferece uma forma mais genérica para observar e reagir a mudanças de dados em uma instância: observadores (em inglês, watchers). Quando se tem alguns dados que necessitam mudar com base na alteração de outros dados, é tentador usar excessivamente o watch - especialmente se você está vindo do AngularJS. No entanto, frequentemente é melhor usar um dado computado em vez do watch. Considere este exemplo:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

O código acima é imperativo e repetitivo. Compare-o com a versão com dados computados:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

Muito melhor, não é mesmo?

Atribuição em Dados Computados

Dados computados são por padrão getter-only (somente retornam valor), mas é possível fornecer um setter se precisar dele:

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names.shift()
      this.lastName = names.pop()
    }
  }
}
// ...

Se você executar vm.fullName = 'John Doe', o setter será automaticamente executado, fazendo com que vm.firstName e vm.lastName sejam atribuídos corretamente.

Observadores

Enquanto dados computados são mais adequados na maioria dos casos, há momentos em que um observador personalizado é necessário. Por isso o Vue fornece uma maneira mais genérica para reagir a alterações de dados, o watch. Isto é particularmente útil quando se precisa executar operações assíncronas ou operações complexas antes de responder a uma alteração de dados.

Por exemplo:

<div id="watch-example">
  <p>
    Faça uma pergunta do tipo sim/não:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<!-- Como já existe um rico ecossistema de bibliotecas para Ajax     -->
<!-- e de coleções de métodos utilitários com propósitos gerais, o   -->
<!-- núcleo Vue permanece pequeno não os reinventando. Isto também   -->
<!-- dá a você liberdade de usar apenas o que estiver familiarizado. -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'Eu não posso responder até que você faça uma pergunta!'
  },
  watch: {
    // sempre que a pergunta mudar, essa função será executada
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Esperando você parar de escrever...'
      this.debouncedGetAnswer()
    }
  },
  created: function () {
    // _.debounce é uma função fornecida pelo lodash para limitar
    // a frequência que uma operação complexa pode ser executada.
    // Neste caso, queremos limitar a frequência com que acessamos
    // a yesno.wtf/api, esperando que o usuário termine completamente
    // a digitação antes de realizar a chamada Ajax. Para aprender
    // mais sobre a função _.debounce (e sua prima _.throttle),
    // visite: https://lodash.com/docs#debounce
    this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
  },
  methods: {
    getAnswer: function () {
      if (this.question.indexOf('?') === -1) {
        this.answer = 'Perguntas geralmente têm uma interrogação. ;-)'
        return
      }
      this.answer = 'Pensando...'
      var vm = this
      axios.get('https://yesno.wtf/api')
        .then(function (response) {
          vm.answer = response.data.answer === 'yes' ? 'Sim.' :
            response.data.answer === 'no' ? 'Não.' : 'Talvez!'
        })
        .catch(function (error) {
          vm.answer = 'Erro! Não pode executar a API. ' + error
        })
    }
  }
})
</script>

Resultado:

Faça uma pergunta do tipo sim/não:

{{ answer }}

Neste caso, utilizar a opção watch nos permitiu realizar uma operação assíncrona (acessar uma API), limitar a frequência com que realizamos essa operação, e definir estados intermediários até que se obtenha a resposta final. Nada disso seria possível com um dado computado.

Além da opção watch, também é possível utilizar a API imperativa vm.$watch.