Store Pinia
Dans ce chapitre, on ajoute un état global partagé au Show Watchlist : useAuthStore pour l'authentification et on connecte isAuthenticated au guard du router.
Pourquoi un store ?
Dans une application Vue, plusieurs composants peuvent avoir besoin des mêmes données : la navbar qui affiche l'utilisateur connecté, le guard du router qui vérifie l'authentification, la page de login qui redirige si déjà connecté…
Gérer ces données via des props et événements devient vite ingérable :
App.vue
├── NavMenu.vue ← affiche le nom de l'utilisateur connecté
├── LoginPage.vue ← redirige si déjà connecté
└── router.ts ← vérifie isAuthenticated avant chaque navigation
Un store centralise ces données dans un état global, accessible depuis n'importe quel composant sans prop drilling.
Installation
npm install pinia
// main.ts
import naive from 'naive-ui'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(naive)
app.mount('#app')
Le type User
On ajoute User dans src/types/index.ts, à côté de Show :
// src/types/index.ts
export interface Show {
id: number
name: string
genres: string[]
summary: string | null
image: { medium: string } | null
rating: { average: number | null }
seen: boolean
}
export interface User {
id: number
email: string
username: string
}
Créer un store
Un store Pinia avec le Setup Store ressemble à un composable, mais son état est global et partagé entre tous les composants.
// src/store/auth.ts
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import type { User } from '@/types'
export const useAuthStore = defineStore('auth', () => {
// État — initialisé depuis le localStorage si disponible
const token = ref<string | null>(localStorage.getItem('token'))
const user = ref<User | null>(
JSON.parse(localStorage.getItem('user') ?? 'null')
)
// Getter
const isAuthenticated = computed(() => !!token.value)
// Actions
const login = (newToken: string, newUser: User): void => {
token.value = newToken
user.value = newUser
localStorage.setItem('token', newToken)
localStorage.setItem('user', JSON.stringify(newUser))
}
const logout = (): void => {
token.value = null
user.value = null
localStorage.removeItem('token')
localStorage.removeItem('user')
}
return { token, user, isAuthenticated, login, logout }
})
Utiliser un store dans un composant
<!-- src/components/NavMenu.component.vue -->
<template>
<NSpace align="center" :size="8">
<RouterLink :to="ROUTES.HOME">
<NButton quaternary :type="route.path === ROUTES.HOME ? 'primary' : 'default'">
Accueil
</NButton>
</RouterLink>
<NButton v-if="isAuthenticated" @click="authStore.logout()">
Déconnexion ({{ user?.username }})
</NButton>
<NButton v-else type="primary" @click="handleLogin">
Se connecter
</NButton>
</NSpace>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useRoute } from 'vue-router'
import { useAuthStore } from '@/store/auth'
import { ROUTES } from '@/router'
const route = useRoute()
const authStore = useAuthStore()
const { user, isAuthenticated } = storeToRefs(authStore)
// Connexion simulée — sera remplacée par un vrai appel API
const handleLogin = () => {
authStore.login('fake-token', { id: 1, email: 'alice@example.com', username: 'Alice' })
}
</script>
storeToRefs
// ❌ Perd la réactivité
const { user, isAuthenticated } = authStore
// ✅ Conserve la réactivité
const { user, isAuthenticated } = storeToRefs(authStore)
// ✅ Les actions se déstructurent directement
const { login, logout } = authStore
Connecter le store au router
On remplace maintenant le booléen statique du chapitre routing par le vrai store :
// src/router.ts
import { useAuthStore } from './store/auth'
router.beforeEach((to) => {
const auth = useAuthStore()
if (to.meta.requiresAuth && !auth.isAuthenticated) {
return ROUTES.HOME
}
return true
})
C'est le cas d'usage idéal du store :
isAuthenticatedest partagé entre le guard, la navbar et les pages — une seule source de vérité.