Outils Vue.js
Vue n’est pas le framework le plus utilisé (c’est surement React), mais c’est l’un des plus simple à appréhender. Et ainsi comprendre le principe global des ces outils.
Documentation#
Commencer#
Pour commencer à utiliser Vue, pas besoin de configuration complexe, il suffit de charger la librairie. Ici on utilisera Vue 3, avec le style «Options API».
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
Et de déclarer une nouvelle instance de Vue avec vos données dans data
(coté JS), et ces mêmes données coté template/rendu (donc HTML)
<div id="app">
{{ message }}
</div>
Vue.createApp({
data() {
return {
message: 'Hello Vue !',
};
},
}).mount('#app');
Notez que data
est une fonction qui retourne un objet littéral contenant nos données.
On parle alors de binding. Les données coté JS sont liées au rendu HTML. Dans Vue, le binding classique se fait avec la notation {{ }}
. Pour binder la valeur à un attribut, il faut utiliser la directive v-bind:
<div v-bind:title="message"></div>
Ou simplement la version raccourcie avec deux-points
<div :title="message"></div>
Pour éviter de mettre trop de logique dans le template (HTML), on peut créer des propriétés calculées, à définir dans l’option computed
(et non plus data
). Par exemple, pour afficher le message en sens inverse
Vue.createApp({
data() {
return {
message: 'Hello Vue !',
};
},
computed: {
reversedMessage () {
// `this` pointe sur l'instance Vue
return this.message.split('').reverse().join('');
},
},
}).mount('#app');
<div id="app">
{{ reversedMessage }}
</div>
Notez que reversedMessage
est une fonction, mais qui est utilisée telle une propriété
Pour les formulaires, il est souvent nécessaire de binder dans les 2 sens. Du code vers l’interface, mais également de l’interface vers le code (case à cocher, champ texte, etc.). Pour cela, on utilisera la directive v-model
.
<input type="text" v-model="message">
<input type="checkbox" v-model="cochee">
Vue.createApp({
data() {
return {
message: 'Hello Vue !',
cochee: true,
};
},
}).mount('#app');
Ainsi, il n’est pas forcément nécessaire de créer d’évènements. Au chargement de la page, le champ texte contient le texte "Hello Vue !"
et la checkbox est cochée. Si l’utilisateur change le texte depuis l’interface, la variable message
est mise à jour dans le code. Si il décoche la checkbox, idem, la variable cochee
est mise à jour. Et les éventuelles propriétés calculées sont recalculées en temps-réel.
Vue propose également d’autres directives intéressantes qui permettent d’ajouter de la logique dans vos templates :
v-if
/v-else
pour de l’affichage conditionnelv-for
pour générer des éléments HTML à la volée
Les évènements#
La directive v-on:
permet d’ajouter des évènements qui appelleront des méthodes stockées dans methods
Vue.createApp({
data() {
return {
message: 'Hello Vue !',
};
},
methods: {
hello () {
alert('Hello !');
},
},
}).mount('#app');
<button v-on:click="hello">Say Hello</button>
Ou simplement la version raccourcie avec @
<button @click="hello">Say Hello</button>
Pour passer des variables, vous utiliserez
<button @click="hello('world', $event)">Say Hello</button>
Il existe également des modificateurs pour gérer quelques actions classiques :
<!-- call event.preventDefault() -->
<form @submit.prevent="submit"></form>
<!-- call submit() when the keyCode is 13 (enter) -->
<input @keyup.13="submit">
Les composants#
Il n’est bien entendu pas souhaitable de mettre toute votre application dans une seule instance Vue. L’un des concepts des frameworks est de découper une interface en composants réutilisables. Chaque composant aura accès à ses propres données, évènements, etc.
let app = Vue.createApp({});
app.component('my-component', {
template: '<div>{{reversedMessage}}</div>',
data () {
return {
message: 'Hello Component !',
};
},
computed: {
reversedMessage () {
return this.message.split('').reverse().join('');
},
},
});
app.mount('#app');
<div id="app">
<my-component></mycomponent>
</div>
Nous donne :
<div id="app">
<div>! tnenopmoC olleH</div> <!-- «Hello Component !» à l’envers-->
</div>
Il est également possible de créer des composants par fichier (.vue
) qui contiennent le HTML, JS et CSS associé. Cela nécessite l’utilisation d’un builder.
Passer des propriétés#
Lorsqu’il est instancié, un composant peut recevoir des propriétés depuis son parent. C’est même d’ailleurs la seule méthode de communication entre éléments (ce que l’on appelle le one-way data flow). Il faut pour cela que le composant enfant les définissent depuis props
(avec ou sans validation du type attendu)
app.component('my-component', {
template: '<div>{{reversedMessage}}</div>',
props: {
message: {
type: String,
default: '...',
},
},
});
<div id="app">
<my-component message="Hello from Parent"></my-component>
<my-component></my-component>
</div>
donne
<div id="app">
<div>Hello from Parent</div>
<div>...</div>
</div>
La communication priviligiée est toujours du composant parent vers le composant enfant. Il est toutefois possible de faire l’inverse, grâce aux évènements. Voici un exemple de composant button-counter
extrait de la doc officielle :
<div id="app">
<p>{{ total }}</p>
<button-counter @increment="incrementTotal"></button-counter>
</div>
let app = Vue.createApp({
data() {
return {
total: 0,
};
},
methods: {
incrementTotal () {
this.total++;
},
},
});
app.component('button-counter', {
template: '<button @click="incrementCounter">+</button>',
methods: {
incrementCounter () {
this.$emit('increment');
},
},
});
app.mount('#app');
Dans des cas simples, cela peut suffire. Dans les autres cas, il peut être préconisé d’utiliser un «gestionnaire d’état» plus global, comme Vuex (voir plus loin).
Cycle de vie#
Chaque composant a un cycle de vie, et des hooks nous permettent d’exécuter des actions à des moments précis. Le plus important étant mounted
app.component('my-component', {
template: '<div>{{message}}</div>',
mounted () {
console.log('Composant attaché (monté) au DOM');
},
});
Il existe également :
created()
le composant est crééupdated()
le composant a reçu de nouvelles propriétés (et donc s’est mis à jour)destroyed()
le composant est supprimé- etc.
Voir : https://vuejs.org/v2/api/#Options-Lifecycle-Hooks
Réactivité#
Bien entendu, le premier intérêt étant la réactivité, un composant se met à jour automatiquement si ses donnés changent ou s’il reçoit de nouvelles données de son parent. Par exemple, un composant heure qui est mis à jour depuis son parent, toutes les 1 secondes.
Vue décide ou non d'appliquer cette mise à jour dans le DOM si nécessaire.
let app = Vue.createApp({
data() {
return {
timeString: '00:00:00',
};
},
mounted () {
this.setTime();
},
methods: {
setTime () {
requestAnimationFrame(() => {
this.timeString = new Date().toLocaleTimeString('fr-FR');
this.setTime();
});
},
},
});
app.component('heure', {
template: '<p>{{time}}</p>',
props: ['time'],
});
app.mount('#app');
<div id="app">
<heure :time="timeString"></heure>
</div>
Exercice - Vue + Composants#
A la suite de l’exercice Tweetbox Vue
- Modifiez votre code pour créer des composants
<Tweet />
- Chaque composant doit recevoir de son parent (l’app) deux propriétés :
- le texte
- l’image
- Chaque tweet doit pouvoir être supprimé. Pour cela :
- Ajoutez un bouton de suppression dans chaque tweet
- Au click sur ce bouton, émettez un évènement (
this.$emit
) - Dans le parent, réagissez à cet évènement pour supprimer ce tweet de la liste des tweets
Vuex#
Nous avons vu que chaque composant gère son propre état, et que nous ne pouvons passer des données que dans un seul sens (il n’est pas possible de facilement envoyer des données d’un composant enfant vers un composant parent). Dans le cas d’une application complexe, avec beaucoup de composants, la maintenance peut devenir difficile car l’état complet de l’application se trouve éparpillé dans de multiple composants. C’est là que Vuex intervient.
Vuex est l’implémentation de Flux pour Vue. Flux est une méthode de structuration des données et des méthodes d’échanges entre composants.
Pour faire simple, Vuex gère un état commun pour l’intégralité de l’application, ainsi que le respect des méthodes de mises à jour. La mise à jour des données ne doit se faire qu’en respectant ce principe, le fameux one-way data flow.
Cette image en explique le principe complet de fonctionnement. Pour mettre à jour l’état de l’application, un composant doit :
- appeller une action (dispatch) : cette action peut faire appel à un backend et peut être asynchrone
- demander le changement d’état (commit) de manière synchrone (soit le composant soit l’action)
- ce changement d’état modifie l’état global (mutate) de manière synchrone
- ce changement provoque la mise à jour des composants dépendants, de manière asynchrone
Et ainsi de suite
Voici un exemple simplifié de l’utilisation de Vuex (ne pas oublier de charger la librairie)
<script src="https://unpkg.com/vuex"></script>
// le fameux état global, s’appelle un store
// contient:
// - state: l’état
// - mutations: les changements d’état possible
let store = Vuex.createStore({
state() {
return {
count: 0,
};
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--,
},
});
Vue.createApp({
store: store,
computed: {
count () {
return store.state.count;
},
},
methods: {
increment () {
store.commit('increment');
},
decrement () {
store.commit('decrement');
},
},
}).mount('#app');
<div id="app">
<p>{{ count }}</p>
<p>
<button @click="increment">+</button>
<button @click="decrement">−</button>
</p>
</div>
L’intérêt étant qu’un autre composant puisse interagir simplement avec ces données
app.component('five', {
template: '<div>{{ isFive }}</div>',
computed: {
isFive () {
return store.state.count === 5 ? 'Five!!!' : 'Not five';
},
},
});
<div id="app">
...
<five/>
</div>
Pour un confort d’utilisation de Vuex, il est recommandé d’utiliser un builder.
Boilerplate App#
Pour construire une application structurée et donc maintenable, il est préférable de recourir à un outil de build. Pour Vue, il est possible d’utiliser create-vue, basé sur Vite.
npm create vue@latest
Il sera alors possible d’utiliser des fichiers avec l’extension .vue
, qui seront compilés en HTML, CSS et JavaScript. Voici un exemple de fichier .vue
// Tweet.vue
<template>
<div>
<p>Hello</p>
</div>
</template>
<script>
export default {
data() {
return {
foo: 42
}
},
computed: {
},
methods: {
},
}
</script>
<style scoped>
p {
color: blue;
}
</style>