Vue

Composants

Comprendre les composants dans Vue 3 et comment les utiliser efficacement

Création et utilisation d'un composant

Les composants sont des éléments réutilisables qui permettent de structurer une application Vue en morceaux indépendants. Il s'agit d'un fichier .vue qui a vocation à être importé ou réutilisé. Imaginons dans notre cas que nous ayons extrait la logique de notre Counter précédemment et que nous cherchons à l'importer.

// Counter.component.vue
<script setup lang="ts">
import { ref } from 'vue';

const counter = ref(0);
const doubleCounter = computed(() => counter.value * 2);

const reset = () => {
  counter.value = 0;
};
</script>

<template>
  <div>
    <p>Compteur : {{ counter }}</p>
    <p>Double compteur : {{ doubleCounter }}</p>
    <button @click="counter++">Incrémenter</button>
    <button v-bind:disabled="counter===0" @click="reset">Réinitialiser</button>
  </div>
</template>
// App.vue
<script setup>
  import {ref} from 'vue';
  import CounterComponent from './components/counter.component.vue';

  const connectedUser = ref(null);
  const users = ref([
    {name: 'John', age: 25},
    {name: 'Jane', age: 24},
    {name: 'Jack', age: 30},
  ]);
</script>

<template>
  <div
  >
    <CounterComponent/>
    <div>
      <p v-if="connectedUser">Connected User : {{ connectedUser }}</p>
      <ul>
        <li v-for="user in users" :key="user.name">
          {{ user.name }} - {{ user.age }} years
        </li>
      </ul>
      <input v-model="connectedUser" type="text">
    </div>
  </div>
</template>

<style scoped>
  div {
    margin-top: 2em;
    display: block
  }
</style>

Props: Passer des données aux composants

Les props permettent de transmettre des données d'un composant parent à un composant enfant.

Dans notre cas nous allons modifier la logique de noter code lié aux utilisateurs. Je voudrais un composant qui prend en paramètre une liste d'utilisateurs et les affiche sous forme de liste.

Composant Enfant (UserCollection.component.vue)

<template>
  <div class="user-list">
    <h2>Liste des utilisateurs</h2>
    <ul>
      <li v-for="user in users" :key="user.name">
        {{ user.name }} - {{ user.age }} ans
      </li>
    </ul>
  </div>
</template>

<script lang="ts" setup>
  import {defineProps} from 'vue';
  import type {User} from '../types/common.type.ts';

  const props = defineProps({
    users: {
      type: Array as () => User[],
      required: true,
    },
  });
</script>

Composant Parent (App.vue)

<script lang="ts" setup>
  import {ref} from 'vue';
  import CounterComponent from './components/Counter.component.vue';
  import UserCollection from './components/UserCollection.component.vue';
  import type {User} from './types/common.type.ts';

  const connectedUser = ref(null);
  const users = ref<User[]>([
    {name: 'John', age: 25},
    {name: 'Jane', age: 24},
    {name: 'Jack', age: 30},
  ]);

</script>

<template>
  <div
  >
    <CounterComponent/>

    <div>
      <p v-if="connectedUser">Connected User : {{ connectedUser }}</p>
      <UserCollection :users="users"/>
      <input v-model="connectedUser" type="text">
    </div>
  </div>
</template>

<style scoped>
  div {
    margin-top: 2em;
    display: block
  }
</style>

On renseigne la props users dans le composant parent App.vue et on l'utilise dans le composant enfant UserCollection.component.vue.

Émission d'événements entre composants

Lorsqu'un composant enfant doit envoyer une information au parent, on utilise $emit.

Supposons que lors d'un click sur l'un items de la liste utilisateurs je souhaite afficher cet utilisateur dans mon composant parent.

Composant Enfant (UserCollection.component.vue)

<template>
  <button @click="envoyerMessage">Cliquez-moi</button>
</template>

<script>
export default {
  methods: {
    envoyerMessage() {
      this.$emit("messageEnvoye", "Bonjour depuis le composant enfant !");
    }
  }
};
</script>

Composant Parent (App.vue)

<script lang="ts" setup>
  import {ref} from 'vue';
  import CounterComponent from './components/Counter.component.vue';
  import UserCollection from './components/UserCollection.component.vue';
  import type {User} from './types/common.type.ts';

  const selectedUser = ref<User | null>(null);
  const users = ref<User[]>([
    {name: 'John', age: 25},
    {name: 'Jane', age: 24},
    {name: 'Jack', age: 30},
  ]);

  const userReceived = (user: User) => {
    selectedUser.value = user;
  };

</script>

<template>
  <div
  >
    <CounterComponent/>

    <div>
      <p v-if="selectedUser">Selected User : {{ selectedUser.name }}</p>
      <UserCollection :users="users" @send-user="userReceived"/>
    </div>
  </div>
</template>

<style scoped>
  div {
    margin-top: 2em;
    display: block
  }
</style>

Slots : insérer du contenu personnalisé

Les slots permettent de passer du contenu HTML au sein d'un composant. On peut même appeler d'autres composants à l'intérieur d'un slot.

Composant Enfant (UserCollection.component.vue)

<template>
  <div class="user-list">
    <h2>Liste des utilisateurs</h2>
    <ul>
      <li v-for="user in users" :key="user.name" @click="sendUser(user)">
        {{ user.name }} - {{ user.age }} ans
      </li>
    </ul>

    <slot></slot>
  </div>
</template>

<script lang="ts" setup>
  import {defineProps} from 'vue';
  import type {User} from '../types/common.type.ts';

  const props = defineProps({
    users: {
      type: Array as () => User[],
      required: true,
    },
  });

  const emit = defineEmits(['sendUser']);

  const sendUser = (user: User) => {
    emit('sendUser', user);
  };
</script>

Composant Parent (App.vue)

<script lang="ts" setup>
  import {ref} from 'vue';
  import CounterComponent from './components/Counter.component.vue';
  import UserCollection from './components/UserCollection.component.vue';
  import type {User} from './types/common.type.ts';

  const selectedUser = ref<User | null>(null);
  const newUser = ref({name: '', age: 0});

  const users = ref<User[]>([
    {name: 'John', age: 25},
    {name: 'Jane', age: 24},
    {name: 'Jack', age: 30},
  ]);

  const userReceived = (user: User) => {
    selectedUser.value = user;
  };

  const addUser = () => {
    if (newUser.value.name && newUser.value.age) {
      users.value.push({...newUser.value});
    }
  };

</script>

<template>
  <div
  >
    <CounterComponent/>

    <div>
      <p v-if="selectedUser">Selected User : {{ selectedUser.name }}</p>
      <UserCollection :users="users" @send-user="userReceived">
        <div class="form-group">
          <input v-model="newUser.name" placeholder="Nom"/>
          <input v-model="newUser.age" placeholder="Âge" type="number"/>
          <button @click="addUser">Valider</button>
        </div>
      </UserCollection>
    </div>
  </div>
</template>