Il y a quelques mois nous avons eu la chance dâassister Ă la plus grande et la plus importante confĂ©rence Vue.js du monde : Vue.js Amsterdam đđ»
Nous avons Ă©tĂ© enthousiasmĂ©s par tout ce que nous y avons vu (sans mauvais jeu de mot), dĂ©couvert et appris. En particulier, sur Pinia, une bibliothĂšque de gestion dâĂ©tat pour Vue.js, qui a suscitĂ© notre intĂ©rĂȘt et notre motivation Ă Ă©crire un article de blog Ă ce sujet.
« BonâŠÂ » vous me direz, elle bien jolie ton histoire, mais câest quoi Pinia exactement ?
Moins de contexte et plus de dev ? Vous pouvez directement vous dirigez sur la partie đInstaller Pinia sur votre projet đ.
Dans cet article nous nous concentrerons sur lâutilisation de Pinia au sein dâune application Vue.js en version 3 (la fin du support de la version 2 de Vue.js Ă©tant annoncĂ© pour le 31 dĂ©cembre 2023).
Pinia câest quoi ? đ€
Et si on commençait par le commencement. On en a un peu parlĂ© dans lâintroduction mais un peu plus de dĂ©tail ne fera pas de mal đ
Pinia est une librairie de gestion dâĂ©tat pour Vue.js. Elle permet de centraliser des Ă©tats appelĂ©s states (ce sont des variables contenant les donnĂ©es de votre application) en un mĂȘme endroit appelĂ© store.
Ce store agit comme un conteneur accessible partout dans lâapplication (pages, composants, composable, âŠ) et tout le monde peut lire (par le biais du state ou de getters) et modifier (par le biais dâactions) son contenu. Pas de panique, ces notions seront dĂ©taillĂ©es par la suite đ
La gestion de lâĂ©tat dâune application Vue.js peut devenir complexe du fait des multiples parties dâĂ©tat rĂ©parties Ă travers divers composants et de leurs interactions. Câest ainsi que des librairies de gestion dâĂ©tat comme Pinia ont pu voir le jour đââïž
Maintenant quâon sait ce quâest Pinia, vous devez sĂ»rement vous demander pourquoi Pinia et pas une autre librairie de gestion dâĂ©tat (vous avez peut-ĂȘtre entendu parler de Vuex par exemple) ? Ăa tombe bien, câest la prochaine partie de cet article.
Pourquoi Pinia ? đ§
Pinia se concentre sur la simplicitĂ© et la facilitĂ© dâutilisation, en offrant une syntaxe concise et facile Ă comprendre pour dĂ©finir lâĂ©tat de lâapplication et les mutations. Il est Ă©galement facile de lâintĂ©grer avec dâautres bibliothĂšques et outils Vue.js.
Si vous ĂȘtes un dĂ©veloppeur Vue.js Ă la recherche dâune alternative Ă Vuex, qui soit lĂ©gĂšre et facile Ă utiliser, Pinia pourrait ĂȘtre la solution que vous recherchez.
Rien de mieux quâune bonne vieille checklist pour justifier lâutilisation de quelque chose đ
đ Syntaxe concise
đ Facile Ă prendre en main
đ Compatible Vue2 et Vue3
đ Des APIs de types Composition-API
đ Une solide prise en charge de lâinfĂ©rence de type lorsquâelle est utilisĂ©e avec TypeScript
đ AutocomplĂ©tion pour le JS
đ Fonctionne avec Vue Devtools (on y reviendra đ)
đ Facile Ă intĂ©grer avec dâautres bibliothĂšques et outils Vue.js.
đ Hot Module Replacement
đ RecommandĂ©e par la communautĂ© đ
đ ProposĂ©e dans la CLI pour initier un nouveau projet avec npm init vue@latest
En bref, Pinia offre une grande expĂ©rience dĂ©veloppeur en se concentrant sur la simplicitĂ© et la facilitĂ© dâutilisation tout en offrant une grande expĂ©rience dĂ©veloppeur.
âUne API plus simple avec moins de fiorituresâ
âUne solide prise en charge de lâinfĂ©rence de type lorsquâelle est utilisĂ©e avec TypeScriptâ
âLes utilisateurs actuels connaissent peut-ĂȘtre Vuex, lâancienne bibliothĂšque officielle de gestion dâĂ©tat pour Vue.js. Pinia jouant le mĂȘme rĂŽle dans lâĂ©cosystĂšme, Vuex est dĂ©sormais en mode maintenance. Elle fonctionne toujours, mais ne proposera plus de nouvelles fonctionnalitĂ©s. Il est recommandĂ© dâutiliser Pinia pour les nouvelles applications.â
(Source : documentation officielle vuejs.org)
Cette recommandation est dâailleurs de plus en plus visible dans la communautĂ© Vue.js. Ă titre dâexemple, lors des confĂ©rences Ă Amsterdam, nous avons entendu parler de Pinia, mais pas de Vuex. Pinia est maintenant la librairie officiellement recommandĂ©e pour la gestion dâĂ©tat dâune application VueJS.
Pourquoi pas directement avec Vue.js ?
Aujourdâhui il est possible de crĂ©er au sein de son application un state global rĂ©actif partagĂ© (un store) sans dĂ©pendances externes. On retrouve principalement deux maniĂšres de faire.
Une premiĂšre approche serait de partager nos informations via un passage de props en profondeur (ou props drilling en anglais). LâidĂ©e est dâinjecter nos props dans tous nos composants, dans toute lâapplication.
Un exemple en image :
(Source : https://vuejs.org/guide/components/provide-inject.html#prop-drilling)
Petit problĂšme avec cette solution, nos props non utilisĂ©es vont se retrouver Ă transiter un peu partout dans des composants qui nâen nâont pas lâutilitĂ©. Sur une petite application, pourquoi pasâŠDans une application plus grande, il sera facile de sây perdre.
La seconde approche est lâutilisation de provide/inject. La documentation officielle de Vue.js rĂ©sume celle-ci de la maniĂšre suivante :
âUn composant parent peut servir de fournisseur de dĂ©pendances pour tous ses descendants. Tout composant enfant de lâarborescence, quelle que soit sa profondeur, peut injecter les dĂ©pendances fournies par des composants prĂ©sents dans sa chaĂźne de composants parents.â
(Source : https://vuejs.org/guide/components/provide-inject.html#prop-drilling)
(Source : https://vuejs.org/guide/components/provide-inject.html#prop-drilling)
Pour fournir des donnĂ©es aux descendants dâun composant, utilisez lâoption provide :
import { provide } from 'vue'
export default {
setup() {
provide(/* key */ 'message', /* value */ 'hello!')
}
}
Pour récupérer les données fournies dans un provider, il faudra utiliser la fonction inject dans un composant enfant :
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}
Ici, le problĂšme principal sera le mĂȘme quâavec le props drilling : la maintenabilitĂ©. Il pourra ĂȘtre compliquĂ© de traquer les modifications apportĂ©es Ă une variable de notre Ă©tat partagĂ©. De plus, il faudra toujours faire attention Ă se trouver dans un composant sous le provider.
âBah alors, pourquoi je mâembĂȘterais Ă utiliser une solution comme Pinia ?â
TrĂšs bonne question, tu es attentif·ve ! đ
Pour cela, nous reprendrons simplement quelques exemples tirés de la documentation Vue.js qui sont assez parlants
âSi notre solution de gestion dâĂ©tat Ă la main suffit dans les scĂ©narios simples, il y a beaucoup plus dâĂ©lĂ©ments Ă prendre en compte dans les applications de production Ă grande Ă©chelle :
- Des conventions plus solides pour la collaboration en équipe
- LâintĂ©gration avec les Vue Devtools, y compris la timeline, lâinspection des composants et le dĂ©bogage par voyage dans le temps.
- Hot Module Reload
- Prise en charge du rendu cĂŽtĂ© serveurâ
(Source : https://fr.vuejs.org/guide/scaling-up/state-management.html#pinia)
Bon, je ne sais pas ce que tu en dis, mais la thĂ©orie, moi, ça va 5 minutes, il est temps de mettre les mains dans le cambouis đ©âđđ§âđ
Installer Pinia sur votre projet đ
Tout dâabord, ajoutez la dĂ©pendance sur votre projet.
yarn add pinia
// or
npm install pinia
Ensuite il faut créer une instance Pinia et la déclarer à votre application Vue.js.
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
DĂ©sormais vous pouvez utiliser Pinia et crĂ©er votre premier store ! đ„ł
Vue.js Devtools
Avant de dĂ©marrer le dĂ©veloppement de notre store, il est vivement conseillĂ© dâinstaller lâextension Vue Devtools sur votre navigateur (conçu par le crĂ©ateur de Vue.js, Evan You).
Cette extension vous permet de visualiser lâĂ©tat de votre store et de le manipuler directement depuis votre navigateur. Une fois installĂ©e, vous pourrez y accĂ©der dans le panel de dĂ©veloppeurs lorsque vous serez sur votre application en mode DEV.
| đĄ Il est parfois nĂ©cessaire dâajouter la ligne suivante dans le fichier main.ts pour lâactiver: Vue.config.devtools=true |
Lâoutil propose plusieurs fonctionnalitĂ©s utiles lorsque lâon dĂ©veloppe une application Vue.js (avec ou sans stores). Dans le cas de notre store Pinia, on peut constater que le store est automatiquement dĂ©tectĂ© et que nos donnĂ©es sont visibles et Ă©ditables Ă tout instant, offrant ainsi la possibilitĂ© de tester dynamiquement nos pages/vues/composants directement depuis le navigateur.Â
Par exemple, dans le cas suivant il est tout Ă fait possible dâajouter de nouveaux items Ă notre tableau âparkingâ et de vĂ©rifier si les nouveaux Ă©lĂ©ments sâaffichent correctement sur la page.
CrĂ©er son premier store ! đ
Pour comprendre le fonctionnement dâun store sur une application Vue.js, prenons un exemple assez simple: gĂ©rer les donnĂ©es dâune application de rĂ©servation de places de parking.
Lâobjectif Ă©tant de pouvoir lister les places, indiquer si une place de parking est disponible et pouvoir rĂ©server ou libĂ©rer une place.
Le code de lâapplication est disponible Ă lâadresse suivante đ https://github.com/Zenika/pinia-example
Comment modéliser un store ?
Dans votre application Vue.js, vous avez indiquĂ© Ă votre application que Pinia a Ă©tĂ© installĂ©, mais cela ne veut pas dire pour autant quâun store a Ă©tĂ© initialisĂ©.
Un store est tout simplement un fichier stockĂ© dans src/stores oĂč on va appeler la fonction defineStore en lui passant en paramĂštre un nom de store unique (qui correspond au storeId). Dans cet exemple nous voulons modĂ©liser un parking, nous lâappellerons donc parking et parkingStore.
import { defineStore } from 'pinia'
export const parkingStore = defineStore('parking', {})
Et voilĂ , nous avons notre premier store ! â Quoi câest tout ?-, et bien oui, nous avons créé un store pour notre parking, mais il nous manque Ă©videmment quelques Ă©lĂ©ments⊠đ
Introduction aux states
La seconde étape consiste à modéliser les places de parking, qui seront les données manipulées par notre application.
Les places se traduisent de la façon suivante :
export type ParkingPlace = {
id: number,
isAvailable: boolean
}
Le parking se définit par une liste de places, comme ceci :
const parking: Ref<ParkingPlace[]> = ref([
{id:1, isAvailable: true},
{id:2, isAvailable: true},
{id:3, isAvailable: false}
])
Maintenant que nous avons modĂ©lisĂ© en Typescript ce quâest un parking et une place de parking, construisons notre parking dans notre store.
Dans un store, les donnĂ©es sont appelĂ©es âstatesâ, cela peut prendre la forme dâobjet javascript, de nombre, de chaĂźnes de caractĂšres etc.
Ici, nous souhaitons utiliser notre parking en lui attribuant un nombre de places numérotées, regardons ensemble comment le faire avec Pinia :
export const useParkingStore = defineStore('parking', () => {
const parking: Ref<ParkingPlace[]> = ref([
{id:1, isAvailable: true},
{id:2, isAvailable: true},
{id:3, isAvailable: false}
])
return { parking }
})
Nous retrouvons notre propriĂ©tĂ© âstateâ qui contient les donnĂ©es de notre store, ici elle contient uniquement un tableau de places qui correspond Ă notre parking, mais vous pouvez y ajouter dâautres donnĂ©es selon le besoin.
Le store défini ci-dessus est déjà fonctionnel, puisque vous pouvez accéder à votre parking depuis un composant Vue, comme démontré ci-dessous :
<script setup lang="ts">
import { useParkingStore } from '../stores/parking';
const store = useParkingStore()
</script>
<template>
<div>
<div v-for="parkingPlace in store.parking" :key="parkingPlace.id">
<div>{{ parkingPlace.id }}</div>
<div>{{ parkingPlace.isAvailable }}</div>
</div>
</div>
</template>
Pour interagir avec ce conteneur, il y a trois concepts clés à retenir : state, getters et actions.
Introduction aux getters
Grùce aux states nous avons enfin notre parking, désormais nous souhaitons indiquer à notre utilisateur les places encore disponibles, pour cela nous allons faire un getter.
Un getter est une fonction oĂč on va lire nos donnĂ©es et parfois les filtrer, dans notre cas on veut rĂ©cupĂ©rer les places de parking disponibles et rĂ©servĂ©es, soit celles qui ont la propriĂ©tĂ© isAvailable === true et isAvailable === false. Pour cela, on va crĂ©er deux getters qui sâappellent « availableParkingPlaces » et « reservedParkingPlaces », qui rĂ©cupĂšrent le parking stockĂ© dans notre state et filtre ses Ă©lĂ©ments.
const availableParkingPlaces = computed(() => parking.value.filter(place => place.isAvailable))
const reservedParkingPlaces = computed(() => parking.value.filter(place => !place.isAvailable))
Maintenant que les getters sont dĂ©finis dans notre store, utilisons-les dans notre composant Vue pour afficher la liste des places de parking disponibles et rĂ©servĂ©es :Â
<script setup lang="ts">
import { useParkingStore } from '../stores/parking';
const store = useParkingStore()
</script>
<template>
<div class="parking-list">
<h2>Reserved Parking Places</h2>
<div v-for="parkingPlace in store.reservedParkingPlaces" :key="parkingPlace.id">
<div>{{ parkingPlace.id }}</div>
</div>
<h2>Available Parking Places</h2>
<div v-for="parkingPlace in store.availableParkingPlaces" :key="parkingPlace.id">
<div>{{ parkingPlace.id }}</div>
</div>
</div>
</template>
Introduction aux actions
Câest super, notre application commence vraiment Ă prendre forme, mais il reste encore une derniĂšre petite Ă©tape : RĂ©server ou libĂ©rer une place de parking ! Et oui, notre parking pour le moment existe mais nâest pas trĂšs dynamique⊠Pour cela nous allons crĂ©er des actions !
Les actions sont des fonctions qui vont nous permettre de modifier nos states, dans notre cas, la disponibilitĂ© de nos places de parking.Â
Nous allons donc crĂ©er deux fonctions âreserveParkingPlaceâ et âfreeParkingPlaceâ.
export const useParkingStore = defineStore('parking', () => {
...
function reserveParkingPlace(id: ParkingPlace['id']) {
const indexOfParkingPlace = parking.value.findIndex((place) => place.id === id)
if(indexOfParkingPlace >= 0) parking.value[indexOfParkingPlace].isAvailable = false
}
function freeParkingPlace(id: ParkingPlace['id']) {
const indexOfParkingPlace = parking.value.findIndex((place) => place.id === id)
if(indexOfParkingPlace >= 0) parking.value[indexOfParkingPlace].isAvailable = true
}
return {..., reserveParkingPlace, freeParkingPlace }
})
Nos actions sont prĂȘtes, utilisons-les dans notre composant pour interagir avec les listes libres et rĂ©servĂ©es. Pour cela il nous suffit de crĂ©er des boutons qui appelleront nos actions du store au moment de lâĂ©vĂšnement @click.
<script setup lang="ts">
import { useParkingStore } from '../stores/parking';
const store = useParkingStore()
</script>
<template>
<div>
<h2>Reserved Parking Places</h2>
<div v-for="parkingPlace in store.reservedParkingPlaces" :key="parkingPlace.id">
<div>{{ parkingPlace.id }}</div>
<button @click="store.freeParkingPlace(parkingPlace.id)">
Free place
</button>
</div>
<br />
<h2>Available Parking Places</h2>
<div v-for="parkingPlace in store.availableParkingPlaces" :key="parkingPlace.id">
<div>{{ parkingPlace.id }}</div>
<button @click="store.reserveParkingPlace(parkingPlace.id)">
Reserve place
</button>
</div>
</div>
</template>
Et voilĂ , notre store est complet et prĂȘt Ă ĂȘtre utilisĂ© đ
Vous avez maintenant toutes les cartes en main pour dĂ©marrer rapidement avec Pinia đȘ
Merci de nous avoir lu, nous espérons que cet article vous aura plu.
On vous dit Ă bientĂŽt pour de nouvelles aventures đ

