# Propriétés computed et les Observateurs

# Propriété computed

Les expressions dans le template sont très pratiques, mais elles sont destinées à des opérations simples. Mettre trop de logique dans vos templates peut les rendre gonflés et difficiles à maintenir. Par exemple, si nous avons un objet avec un tableau imbriqué:

Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Guide Avancé',
          'Vue 3 - Guide Basique',
          'Vue 4 - Le Mystère'
        ]
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Et nous voulons afficher différents messages selon que author a déjà des livres ou non

<div id="computed-basics">
  <p>A publié des livres:</p>
  <span>{{ author.books.length > 0 ? 'Oui' : 'Non' }}</span>
</div>
1
2
3
4

À ce stade, le template n'est plus simple et déclaratif. Il faut le regarder une seconde avant de se rendre compte qu'il effectue un calcul en fonction de author.books. Le problème est aggravé lorsque vous souhaitez inclure ce calcul dans votre template plus d'une fois.

C'est pourquoi pour une logique complexe qui inclut des données réactives, vous devez utiliser une propriété computed.

# Exemple Basique

<div id="computed-basics">
  <p>A publié des livres:</p>
  <span>{{ publishedBooksMessage }}</span>
</div>
1
2
3
4
Vue.createApp({
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Guide Avancé',
          'Vue 3 - Guide Basique',
          'Vue 4 - Le Mystère'
        ]
      }
    }
  },
  computed: {
    // un getter calculé avec computed
    publishedBooksMessage() {
      // `this` pointe vers l'instance vm 
      return this.author.books.length > 0 ? 'Oui' : 'Non'
    }
  }
}).mount('#computed-basics')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Résultat:

See the Pen Computed basic example by Vue (@Vue) on CodePen.

Ici nous avons déclaré une propriété computed publishedBooksMessage.

Essayez de changer la valeur du tableau books dans le data de l'application et vous verrez comment publishedBooksMessage change en conséquence.

Vous pouvez lier des données à des propriétés computed dans des templates comme une propriété normale. Vue est conscient que vm.publishedBooksMessage dépend de vm.author.books, donc il mettra à jour toutes les liaisons qui dépendent de vm.publishedBooksMessage lorsque vm.author.books change. Et la meilleure partie est que nous avons créé cette relation de dépendance de manière déclarative: la fonction getter calculée n'a pas d'effets secondaires, ce qui la rend plus facile à tester et à comprendre.

# Mise en cache computed vs methods

Vous avez peut-être remarqué que nous pouvons obtenir le même résultat en appelant une méthode dans l'expression:

<p>{{ calculateBooksMessage() }}</p>
1
// dans le composant
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Oui' : 'Non'
  }
}
1
2
3
4
5
6

Au lieu d'une propriété computed, nous pouvons définir la même fonction qu'une méthode. Pour le résultat final, les deux approches sont en effet exactement les mêmes. Cependant, la différence est que les propriétés computed sont mises en cache en fonction de leurs dépendances réactives. Une propriété computed ne réévaluera que lorsque certaines de ses dépendances réactives auront changé. Cela signifie que tant que author.books n'a pas changé, l'accès multiple à la propriété computed publishedBooksMessage renverra immédiatement le résultat calculé précédemment sans avoir à exécuter à nouveau la fonction.

Cela signifie également que la propriété computed suivante ne sera jamais mise à jour, car Date.now () n'est pas une dépendance réactive:

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

En comparaison, un appel de method exécutera toujours la fonction chaque fois qu'un nouveau rendu se produit.

Pourquoi avons-nous besoin de la mise en cache? Imaginez que nous ayons une propriété computed coûteuse en ressource list, qui nécessite de parcourir un énorme tableau et de faire beaucoup de calculs. Ensuite, nous pouvons avoir d'autres propriétés computed qui dépendent à leur tour de list. Sans la mise en cache, nous exécuterions le getter de list beaucoup plus de fois que nécessaire! Dans les cas où vous ne voulez pas de mise en cache, utilisez plutôt method.

# Setter des propriétés computed

Les propriétés computed sont par défaut uniquement en lecture, mais vous pouvez également fournir un setter lorsque vous en avez besoin:

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

Maintenant, lorsque vous exécutez vm.fullName = 'John Doe', le setter sera appelé et vm.firstName et vm.lastName seront mis à jour en conséquence.

# Observateurs

Bien que les propriétés computed soient plus appropriées dans la plupart des cas, il arrive parfois qu'un observateur personnalisé soit nécessaire. C'est pourquoi Vue fournit un moyen plus générique de réagir aux changements de données via l'option watch. Ceci est particulièrement utile lorsque vous souhaitez effectuer des opérations asynchrones ou coûteuses en réponse à la modification des données.

Par exemple:

<div id="watch-example">
  <p>
    Poser une question qui se repond par oui/non:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</div>
1
2
3
4
5
6
7
<!-- Puisqu'il existe déjà un riche écosystème de bibliothèques ajax  -->
<!-- et des collections de méthodes utilitaires générales, Vue core  -->
<!-- est capable de rester petit en ne les réinventant pas. Ceci aussi   -->
<!-- vous donne la liberté d'utiliser ce que vous connaissez.      -->
<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: 'Les questions contiennent généralement un point d\'interrogation. ;-)'
      }
    },
    watch: {
      // chaque fois que question change, cette fonction s'exécute
      question(newQuestion, oldQuestion) {
        if (newQuestion.indexOf('?') > -1) {
          this.getAnswer()
        }
      }
    },
    methods: {
      getAnswer() {
        this.answer = 'Je réfléchis...'
        axios
          .get('https://yesno.wtf/api')
          .then(response => {
            this.answer = response.data.answer
          })
          .catch(error => {
            this.answer = 'Erreur pas de reponse de l\'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

Résultat:

See the Pen Watch basic example by Vue (@Vue) on CodePen.

Dans ce cas, l'utilisation de l'option watch nous permet d'effectuer une opération asynchrone (accès à une API) et définit une condition pour effectuer cette opération. Rien de tout cela ne serait possible avec une propriété computed.

En plus de l'option watch, vous pouvez également utiliser l'API impérative vm.$watch.

# Computed vs Watch

Vue fournit un moyen plus générique d'observer et de réagir aux changements de données sur une instance active courante: les propriétés watch. Lorsque vous avez des données qui doivent changer en fonction d'autres données, il est tentant de surutiliser watch - surtout si vous venez d'un arrière-plan AngularJS. Cependant, il est souvent préférable d'utiliser une propriété computed plutôt qu'un callback impératif watch. Prenons cet exemple:

<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

Le code ci-dessus est impératif et répétitif. Comparez-le avec une version de propriété computed:

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

Beaucoup mieux, non?

Deployed on Netlify.
Dernière Mise-à-jour: 10/12/2021, 11:00:31 AM