# Dados Computados e Observadores

# Dados Computados

Originalmente, Propriedades Computadas (Computed Properties). Entretanto, a adaptação "Dados Computados" foi eleita devido suas utilizações em templates serem efetivamente similares às utilizações de data.

Aprenda como dados computados funcionam com uma aula gratuita na Vue School

Expressões dentro de templates são muito convenientes, mas são destinadas à realização de operações simples. Adicionar muita lógica em seus templates podem torná-los abarrotados de código e dificultar a manutenção. Vamos tomar como exemplo o seguinte objeto, com um vetor (array) incluso:

Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Guia Avançado ',
          'Vue 3 - Guia Básico',
          'Vue 4 - O Mistério'
        ]
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Desejamos mostrar diferentes mensagens, ao verificarmos se author possuir livros (books) ou não:

<div id="computed-basics">
  <p>Possui livros publicados:</p>
  <span>{{ author.books.length > 0 ? 'Sim' : 'Não' }}</span>
</div>
1
2
3
4

Deste modo, o template já não é mais simples e declarativo. Você sempre terá que observar este pedaço de código por um momento para, então, entender que ele realiza um cálculo baseando-se na quantidade de itens disponíveis em author.books. O problema se agravará ainda mais se você desejar incluir mais de uma vez este mesmo cálculo no seu template.

É por razões como estas — para lógicas complexas que dependem de dados reativos — que você deve utilizar dados computados (computed properties).

# Exemplo Básico

<div id="computed-basics">
  <p>Possui livros publicados:</p>
  <span>{{ publishedBooksMessage }}</span>
</div>
1
2
3
4
Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Guia Avançado',
          'Vue 3 - Guia Básico',
          'Vue 4 - O Mistério'
        ]
      }
    }
  },
  computed: {
    // uma função "getter" computada (computed getter)
    publishedBooksMessage() {
      // `this` aponta para a instância Vue `vm` de `Vue.createApp(...)`
      return this.author.books.length > 0 ? 'Sim' : 'Não'
    }
  }
}).mount('#computed-basics')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Resultado:

Veja o exemplo Exemplo básico de Dados Computados por vuejs-br (@vuejs-br) no CodePen.

Aqui declaramos um dado computado (computed property) chamado publishedBooksMessage.

Tente remover os valores do vetor (array) books, encontrado dentro de data, e você visualizará o valor de publishedBooksMessage sendo atualizado.

É possível vincular dados computados em templates como qualquer outro tipo de dado. Vue tem ciência de que vm.publishedBooksMessage depende de vm.author.books para prover seu valor, portanto, ele irá atualizar qualquer vínculo de vm.publishedBooksMessage sempre que vm.author.books sofrer alterações. A melhor parte disto é que criamos tal relação de dependência de forma declarativa: a função getter computada não possui quaisquer efeitos colaterais, tornando-se mais fácil de testar e entender.

# Cache de computed vs. Métodos

Você pode ter notado que é possível obtermos o mesmo resultado ao chamarmos um método, como:

<p>{{ calculateBooksMessage() }}</p>
1
// no componente
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}
1
2
3
4
5
6

Ao invés de utilizar dados computados, podemos obter os mesmos resultados ao simplesmente utilizar métodos. No entanto, a diferença é que dados computados possuem cache de acordo com suas dependências reativas. Um dado computado, portanto, apenas será atualizado quando alguma de suas dependências sofrer alteração. Isto significa, por exemplo, que enquanto author.books não for alterado, o dado computado publishedBooksMessage irá retornar, imediatamente, o último resultado calculado, independente da quantidade de acessos que obter, sem ter de executar a função novamente.

Isto também significa que o dado computado a seguir nunca será atualizado, já que Date.now() não é reconhecido como uma dependência reativa:

computed: {
  now() {
    return Date.now()
  }
}
1
2
3
4
5

Por outro lado, invocar um método sempre irá executar sua função quando ocorrer uma nova renderização.

Por que precisamos de caching? Imagine que temos um dado computado list extremamente custoso, que requer iterar por um extenso vetor (array) e realizar cálculos com seus valores. Além disto, há outros dados computados que dependem de list. Sem realizarmos o cache, list seria executado muito mais vezes do que o necessário! Se você realmente não desejar realizar o cache do valor calculado, utilize um método.

# Atribuição em Dados Computados

Dados computados (computed properties) são, por padrão, getter-only — ou seja, somente retornam valor. Entretanto, também é possível fornecer um setter (Computed Setter), se necessário:

// ...
computed: {
  fullName: {
    // getter
    get() {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Portanto, se executarmos vm.fullName = 'John Doe', o setter será executado e vm.firstName e vm.lastName serão atualizados, respectivamente.

# Observadores

Enquanto dados computados são mais adequados na maioria dos casos, há certos momentos em que um observador (watcher) personalizado se faz necessário. É por isto que o Vue fornece uma maneira mais genérica de reagir à alteração de dados, watch, que se faz útil a partir do momento em que performamos operações assíncronas ou complexas quando certo dado sofre mudanças.

Por exemplo:

<div id="watch-example">
  <p>
    Faça uma pergunta de sim ou não:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</div>
1
2
3
4
5
6
7
<!-- Como já existe coleções com métodos utilitários de     -->
<!-- propósitos gerais e um ecossistema rico de bibliotecas -->
<!-- Ajax, a base do Vue consegue se manter pequena por não -->
<!-- reinventar a roda e dar liberdade para que você possa  -->
<!-- utilizar o que ser mais familiar.                      -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script>
  const watchExampleVM = Vue.createApp({
    data() {
      return {
        question: '',
        answer: 'Perguntas possuem pontos de interrogação ;-)'
      }
    },
    watch: {
      // sempre que `question` sofrer alterações, esta função será executada
      question(newQuestion, oldQuestion) {
        if (newQuestion.indexOf('?') > -1) {
          this.getAnswer()
        }
      }
    },
    methods: {
      getAnswer() {
        this.answer = 'Pensando...'
        axios
          .get('https://yesno.wtf/api')
          .then(response => {
            const responseAnswer = response.data.answer
            this.answer = responseAnswer === 'yes' ? 'Sim.' :
                responseAnswer === 'no' ? 'Não.' : 'Talvez?'
          })
          .catch(error => {
            this.answer = 'Erro! Não foi possível adquirir resultados da API: ' + error
          })
      }
    }
  }).mount('#watch-example')
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

Resultado:

Veja o exemplo Exemplo básico de Observadores por vuejs-br (@vuejs-br) no CodePen.

Neste caso, ao usarmos watch, podemos executar uma operação assíncrona — como adquirir informações de uma API — e, até mesmo, condições para executá-la. Isto não seria possível através da utilização de dados computados (computed properties).

Além da opção de observação watch, você também pode usar a API imperativa vm.$watch.

# Dados Computados vs Observadores

Devido ao Vue oferecer observadores (watchers) como uma maneira mais genérica de observar e reagir à mudanças em dados de uma instância, é tentador utilizar, excessivamente, o watch — especialmente se você possui conhecimentos prévios em AngularJS, por exemplo. No entanto, às vezes, é melhor utilizar um dado computado ao invés de watch. Considere o seguinte exemplo:

<div id="demo">{{ fullName }}</div>
1
const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar',
      fullName: 'Foo Bar'
    }
  },
  watch: {
    firstName(val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName(val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
}).mount('#demo')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

O código acima é imperativo e repetitivo. A seguir, uma versão do código utilizando dados computados, a fim de compará-los:

const vm = Vue.createApp({
  data() {
    return {
      firstName: 'Foo',
      lastName: 'Bar'
    }
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
}).mount('#demo')
1
2
3
4
5
6
7
8
9
10
11
12
13

Muito melhor, não é?