Skip to content

Étape 2 — Extraire le composant PokemonCard

Objectifs

  • Comprendre pourquoi découper en composants (réutilisabilité, lisibilité)
  • Créer un composant avec defineProps()
  • Passer des données du parent à l'enfant via les props

Contexte

Actuellement, tout le code de la carte Pokémon est dans index.vue. Si on veut réutiliser cette carte ailleurs (page favoris, page détail), il faut la dupliquer. La solution : extraire un composant réutilisable.

Avantages :

  • Réutilisable : la même carte sur la page d'accueil, les favoris, etc.
  • Lisible : chaque fichier a une seule responsabilité
  • Maintenable : modifier la carte = modifier un seul fichier

Encart oral — Le build transforme votre code

Question fréquente : « Mon site est-il plus lent si je découpe en composants ? »

Non. Le navigateur ne reçoit jamais vos fichiers .vue. Quand vous lancez npm run build, l'outil Vite transforme tout votre code en quelques fichiers HTML, CSS et JS optimisés :

Que vous ayez 1 fichier ou 50 composants, le résultat du build est sensiblement le même : des fichiers optimisés, compressés et fusionnés. Le découpage en composants est un choix d'organisation pour le développeur, pas de performance pour le navigateur.

Testez vous-même : lancez npm run build et regardez le dossier dist/ — vous ne verrez aucun fichier .vue.

Tâches

1. Créer le composant PokemonCard.vue

Créez le fichier src/components/PokemonCard.vue.

Ce composant :

  • Reçoit un objet pokemon en prop (obligatoire)
  • Affiche la carte (image, nom, niveau) — le même code que dans index.vue
js
defineProps({
  pokemon: {
    type: Object,
    required: true,
  },
})

Encart oral — defineProps

defineProps() déclare les données que le parent envoie à l'enfant. C'est un courrier recommandé : le parent choisit quoi envoyer, l'enfant ne peut que lire (pas modifier).

  • type: Object — la prop doit être un objet
  • required: true — le parent doit fournir cette prop

2. Utiliser PokemonCard dans index.vue

Dans src/pages/index.vue, remplacez le contenu de chaque <v-col> par le composant :

vue
<pokemon-card :pokemon="pokemon" />

Pourquoi l'import de getImageUrl disparaît ?

L'import import { getImageUrl } from '@/utils/imageUrl' n'est plus nécessaire dans index.vue — c'est maintenant PokemonCard.vue qui l'utilise. Quand on extrait du code dans un composant, les imports suivent le code.

Auto-import des composants

Grâce au plugin unplugin-vue-components, les composants dans src/components/ sont auto-importés. Pas besoin d'écrire import PokemonCard from '...'.

Mais pour la clarté pédagogique, on recommande d'ajouter l'import explicitement :

js
import PokemonCard from '@/components/PokemonCard.vue'

3. Créer le composant AppFooter.vue

Créez src/components/AppFooter.vue avec un slot :

  • Utilisez <v-footer> comme conteneur
  • Ajoutez un <slot> pour permettre au parent de personnaliser le contenu
  • Par défaut (si aucun contenu n'est fourni), affichez l'année + "Pokédex"

Encart oral — Slots

Un slot est un trou dans un composant que le parent peut remplir avec du contenu HTML. Si le parent ne fournit rien, le contenu par défaut (entre les balises <slot>) s'affiche.

4. Ajouter AppFooter dans App.vue

Remplacez le <v-footer> existant dans App.vue par <app-footer />.

Tests

  • La grille de Pokémon s'affiche comme avant
  • Le composant PokemonCard reçoit bien la prop (vérifier avec Vue DevTools)
  • Le footer s'affiche avec l'année courante
  • 0 erreurs dans la console

Solutions

src/components/PokemonCard.vue
vue
<template>
  <v-card hover>
    <v-img
      :src="getImageUrl(pokemon.img)"
      :alt="pokemon.name"
      height="200"
      cover
    />
    <v-card-title>{{ pokemon.name }}</v-card-title>
    <v-card-subtitle>Niveau {{ pokemon.level }}</v-card-subtitle>
  </v-card>
</template>

<script setup>
import { getImageUrl } from '@/utils/imageUrl'

defineProps({
  pokemon: {
    type: Object,
    required: true,
  },
})
</script>
src/components/AppFooter.vue
vue
<template>
  <v-footer>
    <div class="px-4 text-center w-100">
      <slot>{{ new Date().getFullYear() }} - Pokédex</slot>
    </div>
  </v-footer>
</template>
src/App.vue (modifications)
vue
<template>
  <v-app>
    <app-header />
    <v-main>
      <router-view />
    </v-main>
    <app-footer />
  </v-app>
</template>

<script setup>
import AppHeader from '@/components/AppHeader.vue'
import AppFooter from '@/components/AppFooter.vue'
</script>
src/pages/index.vue (modifications)
vue
<template>
  <v-container>
    <h1 class="text-h3 text-center my-6">Pokédex</h1>

    <v-row>
      <v-col
        v-for="pokemon in pokemons"
        :key="pokemon.id"
        cols="12"
        sm="6"
        md="4"
        lg="3"
      >
        <pokemon-card :pokemon="pokemon" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script setup>
import PokemonCard from '@/components/PokemonCard.vue'

const pokemons = ref([])

onMounted(async () => {
  const response = await fetch('http://localhost:3535/pokemons')
  pokemons.value = await response.json()
})
</script>

Commit

bash
git add -A
git commit -m "feat: composants PokemonCard et AppFooter"

Documentation pour les cours de développement web