Vue.js

Store Pinia

Gérer l'état global de l'application avec Pinia, le store officiel de Vue 3

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 : isAuthenticated est partagé entre le guard, la navbar et les pages — une seule source de vérité.