Pinia, fossoyeur de Vuex.
Au fil des années, Vuex s'était installé dans la communauté VueJS comme étant le Store de référence. Depuis plus d'un an, il a été détrôné par Pinia, et comme c'est souvent le cas dans la communauté VueJS, ce changement est salvateur !
Simplifier
En vuex, pour réaliser un changement d'une variable contenue dans le store, il fallait passer par une action, celle-ci provoquait une/des mutations, qui modifiais les données du store (le state), ce qui par enchaînement modifiait les valeurs affichées dans nos composants qui utilisaient ce store. Evidement, le développeur devait se garder tout ça en tête...
Ce pattern était directement inspiré du pattern Flux de React et il faut bien avouer qu'en 2016, nous étions bien content en Vue de ne plus compter sur les events entre composants pour synchroniser les données.
Sur de petites applications, Vuex s'en sortait bien, mais sur de grosses applis de plusieurs centaines de milliers de lignes de code, on rigolait un peu moins... Même en utilisant les modules pour segmenter le code, il était parfois complexe de se représenter mentalement le store, tout comme l'effet d'un dispatch qui était un peu "magique" pour les nouveaux venus. Ainsi, le pattern flux bien que nécessaire, étant souvent ressenti comme "lourd" par les développeurs d'applications complexes.
C'est là que Pinia est arrivé comme une brise fraîche et rayonnante d'un matin de printemps.
A propos de l'auteur de Pinia
Eduardo San Martin Morote vit à Paris, ingénieur, il travaille en tant que consultant et développeur open source. Si vous vous dites que ce nom ne vous est pas inconnu, ce serait normal puisqu'il a à son actif beaucoup de projet associé à l'éco-système vue, tel que vue-router et bien d'autres. Il est d'ailleurs membre de la core Team VueJS depuis le début, ce qui vous pose un peu le monsieur.
Si vous souhaitez en savoir d'avantage sur l'aventure de Pinia, DevTheory a fait un podcast à ce sujet
L'objectif d'un store
L'objectif d'un store est d'assurer aux composants que les données proviennent d'une source unique de confiance appellée le Single Point Of Thruth
(SPOT). Pinia va habilement utiliser la fonction ref
de Vue pour réaliser ce SPOT ce qui va éviter la lourdeur des mutations qu'on trouve en Vuex.
Définir un store est assez simple et cerise sur le gâteau, il existe deux façon de faire :
- L'Option Store : qui va vous donner un structure identique à Option API de Vue.
- Le Setup Store : qui va donner une structure comparable à ce qui se trouve dans la fonction "setup" d'un composant en Option API.
Voici quelques exemples, à commencer par l'Option Store :
// Option Store
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Eduardo' }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
On retrouve ici le state
qui va contenir les données du store. Les getters
sont les propriétés auto-calculées à partir du state
à la manière d'un computed
d'un composant. Cette propriété sera accessible en lecture aussi facilement qu'une autre donnée du store.
Enfin on trouve une partie actions
qui sont les fonctions associées au store.
A l'utilisation, dans nos composants ce store d'une simplicité enfantine à utiliser :
<template>
Counter: {{counter.count}}
<button @click="counter.increment()">
Incrémenter
</button>
</template>
<script setup>
import useCounterStore from "./stores/useConterStore.js"
const counter = useCounterStore();
</script>
Si vous avez utilisé massivement Vuex, cette concision de l'accès au state devrait vous remplir les canaux lacrymaux.
Comme cité précédemment, il existe la possibilité d'écrire différemment le store, via le Setup Store
// Setup Store
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})
On retrouve alors la possible de "privatiser" certaines parties du code qui ne seront pas exposées au composants de manière très simple.
Cas d'usage : Persister des variables du store
Un cas d'usage très fréquent est de vouloir persister des données du store dans le navigateur de l'utilisateur. Que ce soit pour des questions de cache ou d'éviter de faire repasser votre utilisateur par la case "login" si ce dernier à le malheur de cliquer sur le bouton refresh du navigateur.
Ce cas d'usage est très simple et élégant à mettre en place en Pinia :
// Option Store
import {useLocalStorage} from "@vueuse/core"
export const useCounterStore = defineStore('counter', {
state: () => ({
count: useLocalStorage('counter_count', 0),
name: 'Eduardo' }
),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
La donnée 'count' sera persistée dans le localStorage
du navigateur via le composable useLocalStorage
, sans que la propriété 'name' ne le soit. Pour en apprendre plus sur la formidable bibliothèque de composables VueUse c'est ici.
Pinia chez NCI
Chez NCI, nous utilisons Pinia comme couche métier côté front. Pour chaque Model
de notre backend, il existe en général un store Pinia associé, permettant d'obtenir une liste (collection) des items du modèle et de manipuler un item en particulier (création / modification / suppression).
On se retrouve alors en front avec quelque chose de très proche des couches métier que nous avons en backend. C'est très fluide à utiliser et assez simple à maintenir.
Pour aller plus loin
Dans tous les cas d'usage que j'ai pu rencontrer, Pinia surpasse à chaque fois Vuex. Sa syntaxe est bien plus élégante et plus intuitive que Vuex ce qui à la relecture / reprise de code est un vrai plus.
Si vous souhaitez en apprendre d'avantage sur Pinia par Eduardo lui-même, il existe cette vidéo ou celle-ci qui explique de manière assez détaillée les différentes possibilités de Pinia.
Si vous avez besoin d'un accompagnement ou d'aide à la mise en place de pinia, NCI sera ravie de vous accompagner.