Imprimer
Catégorie : R4A.10 - Compléments web (vuejs)
Affichages : 469

1°/ Créer un projet vue v3 avec Vite

 1.1°/ Création initiale

npm create vite@latest

1.2°/ Installer les plugins

npm install vue-router
npm install pinia
npm ls
testvue3@0.0.0 ...
├── @vitejs/plugin-vue@4.0.0
├── pinia@2.0.33
├── vite@4.1.4
├── vue-router@4.1.6
└── vue@3.2.47

1.3°/ mettre en place vue-router

import {createRouter, createWebHistory} from 'vue-router'
// import des composants. ATTENTION, mettre le chemin complet avec extension, par ex ../views/MyView.vue

const routes = [
    // définition des routes, comme avec vue v2   
]

const router = createRouter({
  history: createWebHistory(),
  routes, // short for `routes: routes`
})

export default router

 

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router";

createApp(App).use(router).mount('#app')

 

1.4°/ Mettre en place pinia

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router";
import { createPinia } from 'pinia'
const pinia = createPinia()

createApp(App).use(router).use(pinia).mount('#app')
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
    state: () => ({ count: 0, name: 'toto' }),
    getters: {
        doubleCount: (state) => state.count * 2,
    },
    actions: {
        increment() {
            this.count++
        },
    },
})

Remarques :

1.5°/ Lancer le projet

Pour lancer un serveur de développement et tester l'application, il suffit de taper :

npm run dev

 

Pour lancer la compilation d'une version de production, il faut taper :

npm run build

 Le résultat se trouve dans le répertoire dist.

2°/ Coder en vue 3

2.1°/ Définition d'un composant SFC

 

<script setup>
  import { ref } from 'vue'

  const p = defineProps({
    msg: String,
  })

  const count = ref(0)
  console.log(p.msg)
</script>
<script>
  import { ref } from 'vue'
  export default {
    props: { msg: String },
    setup(props) {
      console.log(props.msg)
      const count = ref(0)

      // expose count pour le template
      return {
        count
      }
    }
  }
</script>
  • Même dans ce simple exemple, on voit tout de suite ce qu'apporte setup en facilité d'écriture.

 

  • Si un composant a besoin d'un ou plusieurs autres composants/fichiers, le principe d'importation/utilisation change également un peu, surtout si on utilise Vite comme builder.
  • En effet, par défaut, Vite impose d'utiliser des chemins vers le composant/fichiers avec son extension, et soit absolus dans le système de fichiers, soit relatifs au composant qui veut importer.
  • En revanche, il n'y a plus besoin de champ components pour spécifier que l'on veut utiliser un composant importé dans la partie template. On l'utilise directement.
  • Par exemple, s'il existe un composant components/MyList.vue et que views/ItemsView.vue veut l'importer et l'utiliser dans le template, on écrit :
<template>
  <MyList />
  ...
</template>
<script setup>
  import MyList from '../components/MyList.vue'
  ...
</script>

 

2.2°/ La syntaxe par "composition"

  • Même si les concepts fondamentaux d'observation de variables, de mise à jour automatique du DOM, ... sont toujours là, la syntaxe de codage change radicalement.
  • La syntaxe de vue v2 est appelée par options : la partie script est définie grâce à des "options" telles que props, data, methods, computed, ... qui sont en fait les champs d'un objet définissant le composant.
  • En v3, même si au final on définit également un objet décrivant le composant, on "compose" cet objet comme si on écrivait un script JS, en définissant des variables, des fonctions et des instructions manipulant ces variables et fonctions.
  • C'est la raison pour laquelle il n'y a plus besoin d'utiliser this. comme préfixe chaque fois que l'on manipule une variable ou une fonction du composant.
  • En revanche, ce n'est pas juste un script JS et pour mettre en place les principes fondamentaux : variables observées, computed, watcher, props, etc. vue v3 impose l'utilisation de fonctions décrites dans les section suivantes.

 

  • Cette nouvelle syntaxe apporte également la notion de "composable". Un composable est une fonction permettant de faire un traitement réutilisable dans plusieurs composants, mais qui ne se base pas sur l'état de ces composants pour faire son traitement.
  • Un composable peut en revanche renvoyer un objet réactif au composant qui l'utilise. C'est même souvent le cas.
  • Qui plus est un composable peut en utiliser un autre, comme dans l'exemple de la documentation vue, qui présente un composable permettant de mettre en place l'écoute des événements souris et leur traitement avec un callback, et un autre composable qui l'utilise, afin de récupérer les coordonnées x,y de la souris. Cet exemple est utilisé dans la démonstration téléchargeable.
  • Cela dit, on peut parfaitement se passer des composables, même si c'est bien pratique pour réduire le volume de code des parties script et pour réutiliser du code dans différents composants.

2.3°/ La réactivité

  • Comme en vue 2, un composant définit des variables locales qui sont observées et donc réactives, au sens où leur changement de valeur provoque automatiquement des traitements tels que le rafraîchissement du DOM.
  • En revanche, la déclaration des ces variables locales se fait différemment, grâce aux fonctions : reactive et ref. Ces 2 fonctions doivent être importées pour être utilisée dans <script>
  • La différence entre les 2 est que reactive permet uniquement de définir un objet ou un tableau réactif, alors qu'avec ref, c'est une valeur de n'importe quel type. Cependant, cette valeur est quand même encapsulée dans un objet, dont le champ value est affecté avec cette valeur.
  • Quand on veut manipuler un variable créée avec ref dans <script>, il faut donc utiliser nom_var.value pour accéder à sa valeur. En revanche, dans le <template>, on peut directement utiliser son nom.
  • Exemple :
<script setup>
  import {ref, reactive} from 'vue'
  const maref = ref(23);
  console.log(maref.value) // affiche 23
  const mareact = reactive({count :1})
  console.log(mareact.count) // affiche 1
</script>
<template>
  {{ maref }} = 23
  {{marect.count}}  = 1
</template>

 

Remarque : pour les cas d'utilisation plus complexes (par ex, une ref utilisée dans une reactive), se référer à la documentation.

 

2.4°/ props

  • Comme indiqué plus haut, si on utilise une partie <script setup>,  la déclaration de props se fait par l'intermédiaire d'une fonction : defineProps(). Rq : pas besoin d'importer cette fonction.
  • L'objet fournit en paramètre à cette fonction est le même que celui que l'on définirait avec vue v2 : un objet avec des noms de props et leur type.
  • Parfois, donner le type ne suffit pas et l'on voudrait plutôt voir si la valeur est correcte. Dans ce cas, il est possible de fournir une fonction à la place d'un type. Cette fonction fait office de validateur et doit renvoyer true ou false en fonction de si la valeur est bonne ou non. Si ce n'est pas le cas, un message sera affiché dans la console, comme quand le type ne correspond pas.

 

  • Quand on veut manipuler dans la partie template une props, on fait comme en v2, directement avec le nom de la props.
  • En revanche, quand on veut manipuler la props dans la partie script, il faut forcément utiliser l'objet retourné par defineProps(), comme on peut le voir dans l'exemple de la section 2.1.

2.5°/ computed

  • Pour définir une variable calculée (c.a.d. computed), il faut importer puis utiliser la fonction computed().
  • Cette fonction prend simplement en paramètre une fonction callback qui calcule un résultat. Ce résultat est une variable réactive de type ref.
  • Si cette fonction utilise elle-même des variables réactives, chaque changement de valeurs de celles-ci provoquera un recalcul de la variable calculée, comme en v2.
  • Comme la variable calculée est de type ref, on peut directement utiliser son nom dans le template.

2.6°/ Les fonctions "normales"

  • Plus besoin de créer un attribut methods contenant les fonctions "normales" du composant.
  • Il suffit de les déclarer comme dans un script, avec le mot clé function
  • Elles sont ensuite directement appelable dans la partie template
  • Par exemple :
<scrip setup>
  const count = ref(0)
  function setCount(value ) {
    count.value = value
  }
  ...
<script>
<template>
  ...
  <button @click="setCount(0)">Reset counter</button>
  ...
</template>

 

2.7°/ watcher

  • Pour définir un watcher, il faut importe puis utiliser la fonction watch().
  • Les possibilité sont plus étendues qu'en v2. En effet, on peut observer les changements non pas d'une simple variable mais d'une source, qui peut être une variable, un tableau, le résultat d'une fonction, du moment que cette source est réactive.
  • Le premier paramètre de watch() est la source, et le deuxième est la fonction callback qui est appelée lors d'une modification de la source. Cette fonction prend en paramètre la nouvelle valeur de la source et éventuellement l'ancienne valeur.

ATTENTION : même si les champs d'un objet définit via reactive() sont observés, ils ne sont pas la "source" de la réactivité. Ils ne peuvent donc pas être utilisés comme source. L'astuce consiste à utiliser une fonction qui retourne ces champs. Par exemple :

<script setup>
  import {reactive, watch} from 'vue'
  const o = reactive({name:'toto', age:15})
// on ne peut pas observer directement o.age => on utilise une fct qui renvoie sa valeur
  watch( ()=> o.age, (new, old) => console.log("new value : "+new+", old = "+old))
  ...
</script>

 

2.8°/ Les événements

  • Le principe de "capture" d'un événement, soit natif, soit utilisateur, se fait exactement comme en vue 2, à savoir en utilisant v-on ou son raccourci @, dans la balise qui instancie le composant qui émet l'événement.
  • Par exemple, supposons un composant CompoB qui envoie un événement nommé myevent. CompoA instancie CompoB et veut récupérer l'événement. Dans ce cas, dans CompoA.vue on écrit :
<template>
  ...
  <CompoB @myevent="..."></CompoB>
  ...
</template>

 

  • Pour émettre un évenement depuis la partie <template> d'un composant, le principe reste également le même. On utilise la fonction $emit(). Par exemple, dans CompoB.vue, on écrirait
<template>
  ...
  <button @click="$emit('myevent',value)> Ok </button>
  ...
</template>

 

  • En revanche, pour émettre un événement depuis la partie <script>, le principe est différent.
  • En effet, il faut déclarer l'événement grâce à defineEmits(). Cette fonction prend en paramètre un tableau de nom d'événement, ou bien un objet décrivant l'événement, et renvoie une fonction permettant d'envoyer ces événements. L'usage veut que l'on appelle cette fonction emit.
  • Par exemple :
<script setup>
  ...
  const emit = defineEmits(['myevent'])
  ...
  emit('myevent', 10)
  ...
</script>
  •  Dans cet exemple, on déclare juste le nom de l'événement. Grâce à la syntaxe par objet, il est possible d'associer au nom de l'événement une fonction de validation. Celle-ci sert généralement à tester des conditions pour émettre l'événement ou non. Elle doit retourner true ou false en fonction de si les conditions sont bonnes ou pas.
  • Par exemple :
<script setup>
  ...
  const emit = defineEmits({
    myevent: val => { 
      if (val%2===0) return true 
      else { console.log("invalid value"); return false }
    }
  })
  ...
</script>

 

2.9°/ Le cycle de vie vuejs

  • En v3, les fonctions mounted(), created(), ... ont disparu. Elles ont été remplacées par d'autres qui prennent en paramètre un callback.
  • Par exemple, il y a onBeforeMounted(), onMounted(), onUpdated() ...
  • Il existe également une fonction nextTick() qu'il est possible d'importer est d'utiliser pour attendre la mise à jour du DOM.
  • En conclusion, pour gérer le cycle de vie, c'est le même principe qu'en v2 mais avec une syntaxe légèrement différente.
  • Par exemple :
<scrip setup>
  import {nextTick, onMounted } from 'vue'
  const v = ref(0)
  onMounted( () => v.value = 50 )
  function increment() {
    v.value++
    nextTick(() => { ... do something ...  })
  }
  ...
<script>
  

 

2.10°/ Et le reste ?

  • Globalement, les slots, les directives, l'injection, ... fonctionnement de façon identiques à la v2, à part quelques petites différences et/ou possibilités supplémentaires. 

 

3°/ Démonstration

  • Le code de démonstration est téléchargeable [ ici ]
  • Il contient uniquement le répertoire src.
  • Explorer le code du router et du store pinia : c'est quasi identique à une code en v2 avec vuex.
  • Explorer le code des différents composants : App, TestView, WelcomeView et HelloWorld. Ils illustrent toutes les nouvelles syntaxes exposées en section 2.1 -> 2.9