Introduction a la Composition API Vue.js : Guide Pratique avec Vuex

Apprenez a utiliser la Composition API de Vue 3 pour creer des composants maintenables. Setup, computed, refs et integration Vuex expliques.

Mahmoud DEVO
Mahmoud DEVO
December 28, 2025 11 min read
Introduction a la Composition API Vue.js : Guide Pratique avec Vuex
Table of Contents

Introduction a la Composition API en Vue.js avec Vuex

La Composition API represente une evolution majeure dans la maniere de concevoir des composants Vue.js. Introduite avec Vue 3, elle offre une approche plus flexible et modulaire pour organiser la logique des composants, particulierement lorsqu’elle est combinee avec un gestionnaire d’etat comme Vuex. Dans cet article complet, nous allons explorer en profondeur comment integrer efficacement la Composition API avec Vuex pour creer des applications robustes et maintenables.

Pourquoi Combiner Composition API et Vuex ?

L’integration de la Composition API avec Vuex apporte plusieurs avantages significatifs :

  • Meilleure organisation du code : La logique liee a l’etat global peut etre regroupee avec la logique du composant
  • Reutilisabilite accrue : Les composables peuvent encapsuler la logique Vuex pour une utilisation dans plusieurs composants
  • Typage TypeScript ameliore : La Composition API offre une meilleure inference de types avec TypeScript
  • Testabilite simplifiee : Les fonctions isolees sont plus faciles a tester unitairement
  • Separation claire des responsabilites : Chaque partie de la logique peut etre isolee dans sa propre fonction

Prerequisites

Avant de commencer, assurez-vous d’avoir :

  • Une comprehension solide de Vue.js et de l’Options API
  • Des connaissances de base sur Vuex (state, mutations, actions, getters)
  • Node.js 16+ et npm/yarn installes
  • Un projet Vue 3 configure avec Vuex 4

Comprendre useStore() : Le Point d’Entree

La fonction useStore() est le coeur de l’integration entre la Composition API et Vuex. Elle permet d’acceder a l’instance du store Vuex depuis n’importe quelle fonction setup().

Installation et Configuration

Tout d’abord, installez Vuex 4 compatible avec Vue 3 :

npm install vuex@4

Creation du Store

Voici un exemple complet de store Vuex bien structure :

// src/store/index.ts
import { createStore } from 'vuex';
import type { InjectionKey } from 'vue';
import type { Store } from 'vuex';

// Definition des types pour le state
export interface Listing {
  id: number;
  title: string;
  price: number;
  description: string;
  category: string;
  createdAt: Date;
}

export interface RootState {
  listings: Listing[];
  loading: boolean;
  error: string | null;
  selectedCategory: string;
  searchQuery: string;
}

// Cle d'injection pour TypeScript
export const key: InjectionKey<Store<RootState>> = Symbol();

// State initial
const state: RootState = {
  listings: [],
  loading: false,
  error: null,
  selectedCategory: 'all',
  searchQuery: '',
};

// Mutations
const mutations = {
  SET_LISTINGS(state: RootState, payload: Listing[]) {
    state.listings = payload;
  },
  ADD_LISTING(state: RootState, payload: Listing) {
    state.listings.push(payload);
  },
  REMOVE_LISTING(state: RootState, id: number) {
    state.listings = state.listings.filter(listing => listing.id !== id);
  },
  UPDATE_LISTING(state: RootState, payload: Listing) {
    const index = state.listings.findIndex(l => l.id === payload.id);
    if (index !== -1) {
      state.listings[index] = payload;
    }
  },
  SET_LOADING(state: RootState, payload: boolean) {
    state.loading = payload;
  },
  SET_ERROR(state: RootState, payload: string | null) {
    state.error = payload;
  },
  SET_CATEGORY(state: RootState, payload: string) {
    state.selectedCategory = payload;
  },
  SET_SEARCH_QUERY(state: RootState, payload: string) {
    state.searchQuery = payload;
  },
};

// Actions
const actions = {
  async fetchListings({ commit }: { commit: Function }) {
    commit('SET_LOADING', true);
    commit('SET_ERROR', null);

    try {
      const response = await fetch('/api/listings');
      const data = await response.json();
      commit('SET_LISTINGS', data);
    } catch (error) {
      commit('SET_ERROR', 'Erreur lors du chargement des listings');
      console.error(error);
    } finally {
      commit('SET_LOADING', false);
    }
  },

  async createListing({ commit }: { commit: Function }, listing: Omit<Listing, 'id'>) {
    commit('SET_LOADING', true);

    try {
      const response = await fetch('/api/listings', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(listing),
      });
      const newListing = await response.json();
      commit('ADD_LISTING', newListing);
      return newListing;
    } catch (error) {
      commit('SET_ERROR', 'Erreur lors de la creation');
      throw error;
    } finally {
      commit('SET_LOADING', false);
    }
  },

  async deleteListing({ commit }: { commit: Function }, id: number) {
    try {
      await fetch(`/api/listings/${id}`, { method: 'DELETE' });
      commit('REMOVE_LISTING', id);
    } catch (error) {
      commit('SET_ERROR', 'Erreur lors de la suppression');
      throw error;
    }
  },

  resetFilters({ commit }: { commit: Function }) {
    commit('SET_CATEGORY', 'all');
    commit('SET_SEARCH_QUERY', '');
  },
};

// Getters
const getters = {
  listings: (state: RootState) => state.listings,
  loading: (state: RootState) => state.loading,
  error: (state: RootState) => state.error,

  filteredListings: (state: RootState) => {
    let result = state.listings;

    if (state.selectedCategory !== 'all') {
      result = result.filter(l => l.category === state.selectedCategory);
    }

    if (state.searchQuery) {
      const query = state.searchQuery.toLowerCase();
      result = result.filter(l =>
        l.title.toLowerCase().includes(query) ||
        l.description.toLowerCase().includes(query)
      );
    }

    return result;
  },

  listingCount: (state: RootState) => state.listings.length,

  listingById: (state: RootState) => (id: number) => {
    return state.listings.find(l => l.id === id);
  },

  categories: (state: RootState) => {
    const cats = new Set(state.listings.map(l => l.category));
    return ['all', ...Array.from(cats)];
  },

  hasError: (state: RootState) => state.error !== null,
};

export default createStore<RootState>({
  state,
  mutations,
  actions,
  getters,
});

Utilisation de useStore() dans les Composants

Voici comment utiliser useStore() dans vos composants :

<template>
  <div class="listings-container">
    <div v-if="loading" class="loading-spinner">
      Chargement en cours...
    </div>

    <div v-else-if="error" class="error-message">
      {{ error }}
    </div>

    <div v-else class="listings-grid">
      <div
        v-for="listing in filteredListings"
        :key="listing.id"
        class="listing-card"
      >
        <h3>{{ listing.title }}</h3>
        <p>{{ listing.description }}</p>
        <span class="price">{{ listing.price }} EUR</span>
        <button @click="handleDelete(listing.id)">
          Supprimer
        </button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import { key } from '@/store';

// Acces au store avec typage
const store = useStore(key);

// Acces aux getters via computed
const listings = computed(() => store.getters.listings);
const filteredListings = computed(() => store.getters.filteredListings);
const loading = computed(() => store.getters.loading);
const error = computed(() => store.getters.error);

// Dispatch des actions
const fetchListings = () => {
  store.dispatch('fetchListings');
};

const handleDelete = async (id: number) => {
  if (confirm('Etes-vous sur de vouloir supprimer ce listing ?')) {
    await store.dispatch('deleteListing', id);
  }
};

// Chargement initial
onMounted(() => {
  fetchListings();
});
</script>

Acces aux Getters avec computed()

L’utilisation de computed() pour acceder aux getters Vuex est essentielle pour maintenir la reactivite. Voici les differentes approches :

Approche Basique

import { computed } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();

    // Getter simple
    const listings = computed(() => store.getters.listings);

    // Getter avec parametre
    const getListingById = (id: number) => {
      return computed(() => store.getters.listingById(id));
    };

    return { listings, getListingById };
  }
};

Approche avec Composable Reutilisable

Creer un composable pour encapsuler la logique Vuex :

// src/composables/useListings.ts
import { computed, ComputedRef } from 'vue';
import { useStore } from 'vuex';
import { key, Listing } from '@/store';

interface UseListingsReturn {
  listings: ComputedRef<Listing[]>;
  filteredListings: ComputedRef<Listing[]>;
  loading: ComputedRef<boolean>;
  error: ComputedRef<string | null>;
  listingCount: ComputedRef<number>;
  categories: ComputedRef<string[]>;
  fetchListings: () => Promise<void>;
  createListing: (listing: Omit<Listing, 'id'>) => Promise<Listing>;
  deleteListing: (id: number) => Promise<void>;
  setCategory: (category: string) => void;
  setSearchQuery: (query: string) => void;
  resetFilters: () => void;
}

export function useListings(): UseListingsReturn {
  const store = useStore(key);

  // Getters reactifs
  const listings = computed(() => store.getters.listings);
  const filteredListings = computed(() => store.getters.filteredListings);
  const loading = computed(() => store.getters.loading);
  const error = computed(() => store.getters.error);
  const listingCount = computed(() => store.getters.listingCount);
  const categories = computed(() => store.getters.categories);

  // Actions
  const fetchListings = () => store.dispatch('fetchListings');
  const createListing = (listing: Omit<Listing, 'id'>) =>
    store.dispatch('createListing', listing);
  const deleteListing = (id: number) => store.dispatch('deleteListing', id);

  // Mutations directes (pour les filtres)
  const setCategory = (category: string) =>
    store.commit('SET_CATEGORY', category);
  const setSearchQuery = (query: string) =>
    store.commit('SET_SEARCH_QUERY', query);
  const resetFilters = () => store.dispatch('resetFilters');

  return {
    listings,
    filteredListings,
    loading,
    error,
    listingCount,
    categories,
    fetchListings,
    createListing,
    deleteListing,
    setCategory,
    setSearchQuery,
    resetFilters,
  };
}

Utilisation du Composable

<template>
  <div class="listings-page">
    <header class="page-header">
      <h1>Nos Listings ({{ listingCount }})</h1>

      <div class="filters">
        <select v-model="selectedCategory" @change="onCategoryChange">
          <option v-for="cat in categories" :key="cat" :value="cat">
            {{ cat === 'all' ? 'Toutes categories' : cat }}
          </option>
        </select>

        <input
          v-model="searchQuery"
          type="text"
          placeholder="Rechercher..."
          @input="onSearchChange"
        />

        <button @click="resetFilters">
          Reinitialiser
        </button>
      </div>
    </header>

    <main>
      <ListingGrid
        :listings="filteredListings"
        :loading="loading"
        @delete="deleteListing"
      />
    </main>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useListings } from '@/composables/useListings';
import ListingGrid from '@/components/ListingGrid.vue';

const {
  filteredListings,
  loading,
  listingCount,
  categories,
  fetchListings,
  deleteListing,
  setCategory,
  setSearchQuery,
  resetFilters,
} = useListings();

const selectedCategory = ref('all');
const searchQuery = ref('');

const onCategoryChange = () => {
  setCategory(selectedCategory.value);
};

const onSearchChange = () => {
  setSearchQuery(searchQuery.value);
};

onMounted(() => {
  fetchListings();
});
</script>

Difference entre mapGetters/mapActions et la Composition API

Avec Options API (Ancienne Methode)

<script>
import { mapGetters, mapActions } from 'vuex';

export default {
  computed: {
    ...mapGetters(['listings', 'loading', 'error']),
    ...mapGetters({
      filtered: 'filteredListings',
    }),
  },
  methods: {
    ...mapActions(['fetchListings', 'deleteListing']),
    ...mapActions({
      load: 'fetchListings',
    }),
  },
  mounted() {
    this.load();
  },
};
</script>

Avec Composition API (Nouvelle Methode)

<script setup>
import { computed, onMounted } from 'vue';
import { useStore } from 'vuex';

const store = useStore();

// Equivalent de mapGetters
const listings = computed(() => store.getters.listings);
const loading = computed(() => store.getters.loading);
const error = computed(() => store.getters.error);
const filtered = computed(() => store.getters.filteredListings);

// Equivalent de mapActions
const fetchListings = () => store.dispatch('fetchListings');
const deleteListing = (id) => store.dispatch('deleteListing', id);

onMounted(() => {
  fetchListings();
});
</script>

Tableau Comparatif

AspectOptions API (mapHelpers)Composition API
SyntaxeSpread operator dans computed/methodsFonctions computed individuelles
Typage TypeScriptDifficile a typer correctementExcellent support TypeScript
ReutilisabiliteLimitee au composantComposables reutilisables
TestabiliteNecessite mock du composantFonctions pures testables
LisibiliteLogique disperseeLogique regroupee
PerformanceEquivalenteEquivalente
Tree-shakingMoins efficacePlus efficace
Courbe d’apprentissagePlus simplePlus de concepts

Modules Vuex avec la Composition API

Pour les applications complexes, Vuex permet de diviser le store en modules :

Structure des Modules

// src/store/modules/listings.ts
import type { Module } from 'vuex';
import type { RootState } from '../index';

export interface ListingsState {
  items: Listing[];
  loading: boolean;
  error: string | null;
}

const listingsModule: Module<ListingsState, RootState> = {
  namespaced: true,

  state: () => ({
    items: [],
    loading: false,
    error: null,
  }),

  mutations: {
    SET_ITEMS(state, payload: Listing[]) {
      state.items = payload;
    },
    SET_LOADING(state, payload: boolean) {
      state.loading = payload;
    },
    SET_ERROR(state, payload: string | null) {
      state.error = payload;
    },
  },

  actions: {
    async fetch({ commit }) {
      commit('SET_LOADING', true);
      try {
        const response = await fetch('/api/listings');
        const data = await response.json();
        commit('SET_ITEMS', data);
      } catch (error) {
        commit('SET_ERROR', 'Erreur de chargement');
      } finally {
        commit('SET_LOADING', false);
      }
    },
  },

  getters: {
    all: (state) => state.items,
    byId: (state) => (id: number) => state.items.find(i => i.id === id),
    count: (state) => state.items.length,
  },
};

export default listingsModule;

Acces aux Modules dans la Composition API

// src/composables/useListingsModule.ts
import { computed } from 'vue';
import { useStore } from 'vuex';

export function useListingsModule() {
  const store = useStore();

  // Acces au state du module
  const items = computed(() => store.state.listings.items);
  const loading = computed(() => store.state.listings.loading);
  const error = computed(() => store.state.listings.error);

  // Acces aux getters namespaced
  const allListings = computed(() => store.getters['listings/all']);
  const listingsCount = computed(() => store.getters['listings/count']);

  // Getter avec parametre
  const getById = (id: number) => {
    return computed(() => store.getters['listings/byId'](id));
  };

  // Dispatch d'actions namespaced
  const fetchListings = () => store.dispatch('listings/fetch');

  // Commit de mutations namespaced
  const setError = (error: string | null) => {
    store.commit('listings/SET_ERROR', error);
  };

  return {
    items,
    loading,
    error,
    allListings,
    listingsCount,
    getById,
    fetchListings,
    setError,
  };
}

Migration de Options API vers Composition API

Avant (Options API)

<template>
  <div class="user-profile">
    <h1>{{ fullName }}</h1>
    <p>{{ user.email }}</p>
    <button @click="updateProfile">Mettre a jour</button>
    <button @click="logout">Deconnexion</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
  name: 'UserProfile',

  computed: {
    ...mapState('user', ['user']),
    ...mapGetters('user', ['fullName', 'isAuthenticated']),
  },

  methods: {
    ...mapActions('user', ['updateProfile', 'logout']),
  },

  mounted() {
    if (!this.isAuthenticated) {
      this.$router.push('/login');
    }
  },
};
</script>

Apres (Composition API)

<template>
  <div class="user-profile">
    <h1>{{ fullName }}</h1>
    <p>{{ user?.email }}</p>
    <button @click="handleUpdate">Mettre a jour</button>
    <button @click="handleLogout">Deconnexion</button>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { key } from '@/store';

const store = useStore(key);
const router = useRouter();

// State
const user = computed(() => store.state.user.user);

// Getters
const fullName = computed(() => store.getters['user/fullName']);
const isAuthenticated = computed(() => store.getters['user/isAuthenticated']);

// Actions
const handleUpdate = () => store.dispatch('user/updateProfile');
const handleLogout = async () => {
  await store.dispatch('user/logout');
  router.push('/login');
};

// Lifecycle
onMounted(() => {
  if (!isAuthenticated.value) {
    router.push('/login');
  }
});
</script>

Checklist de Migration

  1. Remplacer data() par ref() ou reactive()
  2. Convertir computed en fonctions computed() importees
  3. Remplacer mapGetters par des computed() individuels
  4. Remplacer mapActions par des fonctions wrapper
  5. Convertir les lifecycle hooks (mounted -> onMounted)
  6. Adapter l’acces a this (plus necessaire dans setup)
  7. Ajouter le typage TypeScript si applicable

Pinia : L’Alternative Moderne a Vuex

Pinia est le successeur officiel de Vuex, recommande par l’equipe Vue pour les nouveaux projets. Voici une comparaison :

Equivalent Pinia du Store Vuex

// src/stores/listings.ts
import { defineStore } from 'pinia';

export interface Listing {
  id: number;
  title: string;
  price: number;
  description: string;
  category: string;
}

export const useListingsStore = defineStore('listings', {
  state: () => ({
    items: [] as Listing[],
    loading: false,
    error: null as string | null,
    selectedCategory: 'all',
    searchQuery: '',
  }),

  getters: {
    filteredListings(): Listing[] {
      let result = this.items;

      if (this.selectedCategory !== 'all') {
        result = result.filter(l => l.category === this.selectedCategory);
      }

      if (this.searchQuery) {
        const query = this.searchQuery.toLowerCase();
        result = result.filter(l =>
          l.title.toLowerCase().includes(query)
        );
      }

      return result;
    },

    count(): number {
      return this.items.length;
    },

    listingById(): (id: number) => Listing | undefined {
      return (id: number) => this.items.find(l => l.id === id);
    },
  },

  actions: {
    async fetchListings() {
      this.loading = true;
      this.error = null;

      try {
        const response = await fetch('/api/listings');
        this.items = await response.json();
      } catch (error) {
        this.error = 'Erreur de chargement';
      } finally {
        this.loading = false;
      }
    },

    async deleteListing(id: number) {
      await fetch(`/api/listings/${id}`, { method: 'DELETE' });
      this.items = this.items.filter(l => l.id !== id);
    },

    setCategory(category: string) {
      this.selectedCategory = category;
    },

    setSearchQuery(query: string) {
      this.searchQuery = query;
    },
  },
});

Utilisation de Pinia avec Composition API

<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useListingsStore } from '@/stores/listings';
import { onMounted } from 'vue';

const store = useListingsStore();

// storeToRefs pour garder la reactivite
const { items, loading, error, filteredListings, count } = storeToRefs(store);

// Actions (pas besoin de storeToRefs)
const { fetchListings, deleteListing, setCategory } = store;

onMounted(() => {
  fetchListings();
});
</script>

Comparaison Vuex vs Pinia

AspectVuex 4Pinia
MutationsObligatoiresPas necessaires
ModulesAvec namespacingStores separes
TypeScriptSupport manuelSupport natif
DevToolsSupport completSupport complet
Taille du bundle~6KB~1.5KB
APIPlus verbosePlus simple
Composition APIVia useStoreNatif
Hot Module ReplacementConfiguration requiseAutomatique

Bonnes Pratiques

1. Toujours Utiliser computed() pour les Getters

// Correct
const listings = computed(() => store.getters.listings);

// Incorrect - perd la reactivite
const listings = store.getters.listings;

2. Creer des Composables pour la Logique Reutilisable

// src/composables/useAuth.ts
export function useAuth() {
  const store = useStore();

  const user = computed(() => store.state.auth.user);
  const isAuthenticated = computed(() => store.getters['auth/isAuthenticated']);

  const login = (credentials) => store.dispatch('auth/login', credentials);
  const logout = () => store.dispatch('auth/logout');

  return { user, isAuthenticated, login, logout };
}

3. Typer Correctement le Store avec TypeScript

// src/store/types.ts
import type { Store } from 'vuex';

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $store: Store<RootState>;
  }
}

4. Utiliser des Constantes pour les Types de Mutations et Actions

// src/store/mutation-types.ts
export const SET_LISTINGS = 'SET_LISTINGS';
export const SET_LOADING = 'SET_LOADING';
export const SET_ERROR = 'SET_ERROR';

// Utilisation
commit(SET_LISTINGS, data);

5. Eviter les Mutations Directes du State

// Incorrect
store.state.listings.push(newListing);

// Correct
store.commit('ADD_LISTING', newListing);

6. Organiser les Stores par Fonctionnalite

src/store/
├── index.ts           # Store principal
├── modules/
│   ├── auth.ts        # Module authentification
│   ├── listings.ts    # Module listings
│   ├── cart.ts        # Module panier
│   └── ui.ts          # Module UI (theme, notifications)
└── types.ts           # Types TypeScript

Pieges Courants a Eviter

1. Oublier d’Importer useStore

// Erreur : useStore is not defined
setup() {
  const store = useStore(); // Erreur si pas importe !
}

// Correct
import { useStore } from 'vuex';

setup() {
  const store = useStore();
}

2. Acceder aux Getters sans computed()

// Piege : la valeur ne sera pas reactive
const listings = store.getters.listings;

// Solution
const listings = computed(() => store.getters.listings);

3. Utiliser this dans setup()

// Piege : this est undefined dans setup()
setup() {
  function toggleDarkMode() {
    this.isDark = !this.isDark; // Erreur !
  }
}

// Solution : utiliser ref
setup() {
  const isDark = ref(false);

  function toggleDarkMode() {
    isDark.value = !isDark.value;
  }

  return { isDark, toggleDarkMode };
}

4. Ne Pas Gerer les Erreurs Async

// Piege : pas de gestion d'erreur
const fetchData = async () => {
  const data = await store.dispatch('fetchListings');
};

// Solution
const fetchData = async () => {
  try {
    await store.dispatch('fetchListings');
  } catch (error) {
    console.error('Erreur:', error);
    // Afficher notification utilisateur
  }
};

5. Destructurer le Store Directement

// Piege avec Pinia : perd la reactivite
const { items, loading } = useListingsStore();

// Solution : utiliser storeToRefs
const store = useListingsStore();
const { items, loading } = storeToRefs(store);

Exemple Complet : Application de Gestion de Listings

Voici un exemple complet integrant tous les concepts :

<!-- src/views/ListingsView.vue -->
<template>
  <div class="listings-view">
    <header class="view-header">
      <h1>Gestion des Listings</h1>
      <p>{{ listingCount }} listing(s) disponible(s)</p>
    </header>

    <section class="filters-section">
      <FilterBar
        v-model:category="selectedCategory"
        v-model:search="searchQuery"
        :categories="categories"
        @reset="handleResetFilters"
      />
    </section>

    <section class="content-section">
      <LoadingSpinner v-if="loading" />

      <ErrorMessage
        v-else-if="error"
        :message="error"
        @retry="handleRetry"
      />

      <ListingGrid
        v-else
        :listings="filteredListings"
        @edit="handleEdit"
        @delete="handleDelete"
      />
    </section>

    <ListingModal
      v-if="showModal"
      :listing="editingListing"
      @save="handleSave"
      @close="closeModal"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import { useListings } from '@/composables/useListings';
import FilterBar from '@/components/FilterBar.vue';
import ListingGrid from '@/components/ListingGrid.vue';
import ListingModal from '@/components/ListingModal.vue';
import LoadingSpinner from '@/components/LoadingSpinner.vue';
import ErrorMessage from '@/components/ErrorMessage.vue';

// Composable pour la logique Vuex
const {
  filteredListings,
  loading,
  error,
  listingCount,
  categories,
  fetchListings,
  createListing,
  updateListing,
  deleteListing,
  setCategory,
  setSearchQuery,
  resetFilters,
} = useListings();

// State local
const selectedCategory = ref('all');
const searchQuery = ref('');
const showModal = ref(false);
const editingListing = ref(null);

// Watchers pour synchroniser avec le store
watch(selectedCategory, (newValue) => {
  setCategory(newValue);
});

watch(searchQuery, (newValue) => {
  setSearchQuery(newValue);
});

// Handlers
const handleResetFilters = () => {
  selectedCategory.value = 'all';
  searchQuery.value = '';
  resetFilters();
};

const handleRetry = () => {
  fetchListings();
};

const handleEdit = (listing) => {
  editingListing.value = { ...listing };
  showModal.value = true;
};

const handleDelete = async (id: number) => {
  if (confirm('Confirmer la suppression ?')) {
    await deleteListing(id);
  }
};

const handleSave = async (listing) => {
  if (listing.id) {
    await updateListing(listing);
  } else {
    await createListing(listing);
  }
  closeModal();
};

const closeModal = () => {
  showModal.value = false;
  editingListing.value = null;
};

// Chargement initial
onMounted(() => {
  fetchListings();
});
</script>

<style scoped>
.listings-view {
  max-width: 1200px;
  margin: 0 auto;
  padding: 2rem;
}

.view-header {
  margin-bottom: 2rem;
  text-align: center;
}

.filters-section {
  margin-bottom: 2rem;
}

.content-section {
  min-height: 400px;
}
</style>

Conclusion

L’integration de la Composition API avec Vuex represente une evolution significative dans le developpement d’applications Vue.js. Cette combinaison offre :

  • Une meilleure organisation du code grace aux composables reutilisables
  • Un typage TypeScript ameliore pour une meilleure maintenabilite
  • Une testabilite accrue avec des fonctions pures et isolees
  • Une flexibilite maximale pour structurer votre logique metier

Pour les nouveaux projets, considerez l’utilisation de Pinia qui offre une API plus simple et un meilleur support natif de la Composition API. Pour les projets existants utilisant Vuex, la migration vers la Composition API peut se faire progressivement, composant par composant.

Les concepts presentes dans cet article vous permettront de creer des applications Vue.js plus robustes, plus maintenables et plus faciles a faire evoluer dans le temps.

Ressources Complementaires

Prochaines Etapes

Pour approfondir vos connaissances, explorez :

  1. La creation de composables complexes avec plusieurs stores
  2. L’integration avec Vue Router pour la gestion de l’etat de navigation
  3. Les patterns avances de gestion d’etat (optimistic updates, cache)
  4. La mise en place de tests unitaires pour les composables Vuex
Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles