Vue Apollo Composable : useQuery et useMutation pour vos operations GraphQL

Exploitez useQuery et useMutation de Vue Apollo Composable pour gerer vos requetes GraphQL. Guide pratique avec gestion du loading, erreurs, cache et optimistic updates.

Mahmoud DEVO
Mahmoud DEVO
December 28, 2025 18 min read
Vue Apollo Composable : useQuery et useMutation pour vos operations GraphQL

Utilisation de GraphQL avec Vue Apollo : Comment effectuer des requetes et des mutations

Introduction

GraphQL a revolutionne la maniere dont les applications frontend communiquent avec leurs backends. Contrairement aux API REST traditionnelles qui exposent des endpoints fixes retournant des structures de donnees predefinies, GraphQL offre un langage de requete flexible permettant aux clients de demander exactement les donnees dont ils ont besoin - ni plus, ni moins.

Vue Apollo Composable est la bibliotheque officielle qui integre Apollo Client avec Vue 3 et sa Composition API. Cette integration permet d’exploiter pleinement la reactivite de Vue tout en beneficiant de la puissance d’Apollo Client pour la gestion du cache, les requetes optimistes et la synchronisation temps reel.

Dans ce guide complet, nous allons explorer en profondeur :

  • L’architecture et les concepts fondamentaux de Vue Apollo
  • L’utilisation de useQuery pour les requetes de lecture
  • L’utilisation de useMutation pour les operations d’ecriture
  • La gestion avancee du cache et des mises a jour optimistes
  • Les patterns de gestion d’erreurs robustes
  • L’integration avec TypeScript pour un typage complet

Pourquoi choisir Vue Apollo Composable ?

CaracteristiqueAvantage
Integration Composition APIUtilisation native des composables Vue 3
Cache intelligentNormalisation et deduplication automatique des donnees
Reactivite completeMise a jour automatique de l’UI lors des changements de donnees
TypeScript firstSupport complet du typage avec generation de types
Optimistic UIMises a jour instantanees de l’interface avant confirmation serveur
SubscriptionsSupport natif des subscriptions GraphQL pour le temps reel

Configuration initiale

Avant de plonger dans les composables, configurons Apollo Client dans notre application Vue 3.

Installation des dependances

# Installation avec npm
npm install @apollo/client @vue/apollo-composable graphql graphql-tag

# Ou avec pnpm
pnpm add @apollo/client @vue/apollo-composable graphql graphql-tag

Configuration du client Apollo

// src/apollo/client.ts
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client/core';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';

// Lien HTTP pour les queries et mutations
const httpLink = new HttpLink({
  uri: import.meta.env.VITE_GRAPHQL_HTTP_ENDPOINT || 'http://localhost:4000/graphql',
  credentials: 'include', // Important pour les cookies d'authentification
});

// Lien WebSocket pour les subscriptions (optionnel)
const wsLink = new GraphQLWsLink(
  createClient({
    url: import.meta.env.VITE_GRAPHQL_WS_ENDPOINT || 'ws://localhost:4000/graphql',
    connectionParams: {
      // Token d'authentification si necessaire
      authToken: localStorage.getItem('token'),
    },
  })
);

// Split le trafic entre HTTP et WebSocket
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

// Creation du client Apollo
export const apolloClient = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache({
    // Configuration du cache pour la normalisation
    typePolicies: {
      Query: {
        fields: {
          listings: {
            // Strategie de fusion pour la pagination
            merge(existing = [], incoming) {
              return [...existing, ...incoming];
            },
          },
        },
      },
    },
  }),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network',
      errorPolicy: 'all',
    },
    query: {
      fetchPolicy: 'network-only',
      errorPolicy: 'all',
    },
    mutate: {
      errorPolicy: 'all',
    },
  },
});

Integration avec Vue 3

// src/main.ts
import { createApp, provide, h } from 'vue';
import { DefaultApolloClient } from '@vue/apollo-composable';
import { apolloClient } from './apollo/client';
import App from './App.vue';

const app = createApp({
  setup() {
    provide(DefaultApolloClient, apolloClient);
  },
  render: () => h(App),
});

app.mount('#app');

Creation de documents GraphQL

Organisation des fichiers GraphQL

Une bonne organisation des fichiers GraphQL facilite la maintenance et la reutilisation.

src/
└── graphql/
    ├── fragments/
    │   └── ListingFragment.ts
    ├── queries/
    │   ├── GetListings.ts
    │   └── GetListingById.ts
    └── mutations/
        ├── CreateListing.ts
        ├── UpdateListing.ts
        └── DeleteListing.ts

Fragments reutilisables

Les fragments permettent de definir des ensembles de champs reutilisables.

// src/graphql/fragments/ListingFragment.ts
import { gql } from 'graphql-tag';

// Fragment de base pour les informations essentielles
export const LISTING_BASIC_FRAGMENT = gql`
  fragment ListingBasic on Listing {
    id
    title
    description
    price
    createdAt
  }
`;

// Fragment complet avec relations
export const LISTING_FULL_FRAGMENT = gql`
  fragment ListingFull on Listing {
    ...ListingBasic
    category {
      id
      name
    }
    author {
      id
      name
      email
      avatar
    }
    images {
      id
      url
      alt
    }
    reviews {
      id
      rating
      comment
      user {
        id
        name
      }
    }
  }
  ${LISTING_BASIC_FRAGMENT}
`;

Document de requete pour recuperer les listes

// src/graphql/queries/GetListings.ts
import { gql } from 'graphql-tag';
import { LISTING_BASIC_FRAGMENT } from '../fragments/ListingFragment';

// Requete avec pagination et filtres
export const GET_LISTINGS = gql`
  query GetListings(
    $first: Int = 10
    $after: String
    $filter: ListingFilterInput
    $orderBy: ListingOrderByInput
  ) {
    listings(
      first: $first
      after: $after
      filter: $filter
      orderBy: $orderBy
    ) {
      edges {
        cursor
        node {
          ...ListingBasic
        }
      }
      pageInfo {
        hasNextPage
        endCursor
        totalCount
      }
    }
  }
  ${LISTING_BASIC_FRAGMENT}
`;

// Requete simple sans pagination
export const GET_ALL_LISTINGS = gql`
  query GetAllListings {
    listings {
      id
      title
      description
      price
      category {
        id
        name
      }
    }
  }
`;

Document pour une requete par ID

// src/graphql/queries/GetListingById.ts
import { gql } from 'graphql-tag';
import { LISTING_FULL_FRAGMENT } from '../fragments/ListingFragment';

export const GET_LISTING_BY_ID = gql`
  query GetListingById($id: ID!) {
    listing(id: $id) {
      ...ListingFull
    }
  }
  ${LISTING_FULL_FRAGMENT}
`;

Utilisation de useQuery

Le composable useQuery est le coeur de Vue Apollo pour les operations de lecture.

Syntaxe de base

// src/composables/useListings.ts
import { computed } from 'vue';
import { useQuery } from '@vue/apollo-composable';
import { GET_ALL_LISTINGS } from '@/graphql/queries/GetListings';

// Interface TypeScript pour le typage
interface Listing {
  id: string;
  title: string;
  description: string;
  price: number;
  category: {
    id: string;
    name: string;
  };
}

interface GetListingsResult {
  listings: Listing[];
}

export function useListings() {
  // Execution de la requete
  const { result, loading, error, refetch, fetchMore } = useQuery<GetListingsResult>(
    GET_ALL_LISTINGS,
    null, // Variables (null si aucune)
    {
      fetchPolicy: 'cache-first', // Strategie de cache
      notifyOnNetworkStatusChange: true, // Mise a jour du loading lors du refetch
    }
  );

  // Extraction reactive des donnees
  const listings = computed(() => result.value?.listings ?? []);

  // Nombre total de listings
  const totalCount = computed(() => listings.value.length);

  return {
    listings,
    totalCount,
    loading,
    error,
    refetch,
    fetchMore,
  };
}

Requete avec variables reactives

// src/composables/useFilteredListings.ts
import { ref, computed, watch } from 'vue';
import { useQuery } from '@vue/apollo-composable';
import { GET_LISTINGS } from '@/graphql/queries/GetListings';

interface ListingFilter {
  categoryId?: string;
  minPrice?: number;
  maxPrice?: number;
  searchTerm?: string;
}

export function useFilteredListings() {
  // Variables reactives pour les filtres
  const filter = ref<ListingFilter>({});
  const pageSize = ref(10);
  const cursor = ref<string | null>(null);

  // Les variables sont reactives - la requete se re-execute automatiquement
  const { result, loading, error, fetchMore } = useQuery(
    GET_LISTINGS,
    () => ({
      first: pageSize.value,
      after: cursor.value,
      filter: filter.value,
      orderBy: { field: 'CREATED_AT', direction: 'DESC' },
    }),
    {
      fetchPolicy: 'cache-and-network',
    }
  );

  // Extraction des donnees paginées
  const listings = computed(() =>
    result.value?.listings?.edges?.map(edge => edge.node) ?? []
  );

  const pageInfo = computed(() => result.value?.listings?.pageInfo);

  const hasNextPage = computed(() => pageInfo.value?.hasNextPage ?? false);

  // Fonction pour charger plus de resultats
  const loadMore = async () => {
    if (!hasNextPage.value || loading.value) return;

    await fetchMore({
      variables: {
        after: pageInfo.value?.endCursor,
      },
      updateQuery: (previousResult, { fetchMoreResult }) => {
        if (!fetchMoreResult) return previousResult;

        return {
          listings: {
            ...fetchMoreResult.listings,
            edges: [
              ...previousResult.listings.edges,
              ...fetchMoreResult.listings.edges,
            ],
          },
        };
      },
    });
  };

  // Fonctions de mise a jour des filtres
  const setFilter = (newFilter: Partial<ListingFilter>) => {
    filter.value = { ...filter.value, ...newFilter };
    cursor.value = null; // Reset pagination lors du changement de filtre
  };

  const clearFilters = () => {
    filter.value = {};
    cursor.value = null;
  };

  return {
    listings,
    loading,
    error,
    hasNextPage,
    totalCount: computed(() => pageInfo.value?.totalCount ?? 0),
    loadMore,
    setFilter,
    clearFilters,
    filter,
  };
}

Utilisation de useResult pour l’extraction de donnees

// src/components/ListingsPage.vue
<script setup lang="ts">
import { useQuery, useResult } from '@vue/apollo-composable';
import { GET_ALL_LISTINGS } from '@/graphql/queries/GetListings';

const { result, loading, error } = useQuery(GET_ALL_LISTINGS);

// useResult permet d'extraire et transformer les donnees
// avec une valeur par defaut si les donnees ne sont pas encore chargees
const listings = useResult(
  result,
  [], // Valeur par defaut
  (data) => data.listings // Fonction de selection
);

// Avec transformation
const listingTitles = useResult(
  result,
  [],
  (data) => data.listings.map(l => l.title)
);

// Extraction conditionnelle
const firstListing = useResult(
  result,
  null,
  (data) => data.listings[0] ?? null
);
</script>

Rendu conditionnel dans le template

Pattern complet de gestion des etats

<!-- src/components/ListingsGrid.vue -->
<template>
  <div class="listings-container">
    <!-- Etat de chargement initial -->
    <div v-if="loading && !listings.length" class="loading-state">
      <div class="skeleton-grid">
        <div v-for="n in 6" :key="n" class="skeleton-card">
          <div class="skeleton-image"></div>
          <div class="skeleton-title"></div>
          <div class="skeleton-description"></div>
        </div>
      </div>
    </div>

    <!-- Etat d'erreur -->
    <div v-else-if="error" class="error-state">
      <div class="error-icon">
        <svg><!-- Icon SVG --></svg>
      </div>
      <h3>Une erreur est survenue</h3>
      <p class="error-message">{{ error.message }}</p>
      <button @click="refetch()" class="retry-button">
        Reessayer
      </button>
    </div>

    <!-- Etat vide (aucun resultat) -->
    <div v-else-if="listings && listings.length === 0" class="empty-state">
      <div class="empty-icon">
        <svg><!-- Icon SVG --></svg>
      </div>
      <h3>Aucune annonce trouvee</h3>
      <p>Modifiez vos filtres ou creez une nouvelle annonce</p>
      <button @click="$emit('create')" class="create-button">
        Creer une annonce
      </button>
    </div>

    <!-- Etat avec donnees -->
    <div v-else class="listings-grid">
      <ListingCard
        v-for="listing in listings"
        :key="listing.id"
        :listing="listing"
        @delete="handleDelete"
        @edit="handleEdit"
      />

      <!-- Indicateur de chargement pour le refetch -->
      <div v-if="loading" class="loading-overlay">
        <span class="spinner"></span>
      </div>
    </div>

    <!-- Pagination / Load More -->
    <div v-if="hasNextPage" class="load-more-section">
      <button
        @click="loadMore"
        :disabled="loading"
        class="load-more-button"
      >
        <span v-if="loading">Chargement...</span>
        <span v-else>Voir plus d'annonces</span>
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { useFilteredListings } from '@/composables/useFilteredListings';
import ListingCard from './ListingCard.vue';

const {
  listings,
  loading,
  error,
  hasNextPage,
  loadMore,
  refetch,
} = useFilteredListings();

const handleDelete = (id: string) => {
  // Logique de suppression
};

const handleEdit = (id: string) => {
  // Logique d'edition
};
</script>

<style scoped>
.listings-container {
  @apply min-h-screen p-6;
}

.listings-grid {
  @apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6;
}

.skeleton-grid {
  @apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6;
}

.skeleton-card {
  @apply bg-gray-200 rounded-lg p-4 animate-pulse;
}

.error-state,
.empty-state {
  @apply flex flex-col items-center justify-center py-16 text-center;
}

.loading-overlay {
  @apply absolute inset-0 bg-white/50 flex items-center justify-center;
}
</style>

Utilisation de useMutation

Le composable useMutation gere toutes les operations d’ecriture GraphQL.

Document de mutation pour la creation

// src/graphql/mutations/CreateListing.ts
import { gql } from 'graphql-tag';
import { LISTING_FULL_FRAGMENT } from '../fragments/ListingFragment';

export const CREATE_LISTING = gql`
  mutation CreateListing($input: CreateListingInput!) {
    createListing(input: $input) {
      ...ListingFull
    }
  }
  ${LISTING_FULL_FRAGMENT}
`;

// Interface pour l'input
export interface CreateListingInput {
  title: string;
  description: string;
  price: number;
  categoryId: string;
  images?: string[];
}

Document de mutation pour la mise a jour

// src/graphql/mutations/UpdateListing.ts
import { gql } from 'graphql-tag';
import { LISTING_FULL_FRAGMENT } from '../fragments/ListingFragment';

export const UPDATE_LISTING = gql`
  mutation UpdateListing($id: ID!, $input: UpdateListingInput!) {
    updateListing(id: $id, input: $input) {
      ...ListingFull
    }
  }
  ${LISTING_FULL_FRAGMENT}
`;

export interface UpdateListingInput {
  title?: string;
  description?: string;
  price?: number;
  categoryId?: string;
  images?: string[];
}

Document de mutation pour la suppression

// src/graphql/mutations/DeleteListing.ts
import { gql } from 'graphql-tag';

export const DELETE_LISTING = gql`
  mutation DeleteListing($id: ID!) {
    deleteListing(id: $id) {
      id
      success
      message
    }
  }
`;

export interface DeleteListingResult {
  deleteListing: {
    id: string;
    success: boolean;
    message?: string;
  };
}

Composable pour les mutations CRUD

// src/composables/useListingMutations.ts
import { ref } from 'vue';
import { useMutation } from '@vue/apollo-composable';
import { CREATE_LISTING, CreateListingInput } from '@/graphql/mutations/CreateListing';
import { UPDATE_LISTING, UpdateListingInput } from '@/graphql/mutations/UpdateListing';
import { DELETE_LISTING, DeleteListingResult } from '@/graphql/mutations/DeleteListing';
import { GET_ALL_LISTINGS } from '@/graphql/queries/GetListings';

export function useListingMutations() {
  const operationError = ref<Error | null>(null);

  // Mutation de creation
  const {
    mutate: createMutate,
    loading: createLoading,
    onDone: onCreateDone,
    onError: onCreateError,
  } = useMutation(CREATE_LISTING, {
    // Mise a jour du cache apres creation
    update: (cache, { data }) => {
      if (!data?.createListing) return;

      // Lecture du cache actuel
      const existingData = cache.readQuery({ query: GET_ALL_LISTINGS });

      if (existingData) {
        // Ajout du nouvel element au cache
        cache.writeQuery({
          query: GET_ALL_LISTINGS,
          data: {
            listings: [...existingData.listings, data.createListing],
          },
        });
      }
    },
  });

  // Mutation de mise a jour avec optimistic response
  const {
    mutate: updateMutate,
    loading: updateLoading,
    onDone: onUpdateDone,
    onError: onUpdateError,
  } = useMutation(UPDATE_LISTING, {
    // Mise a jour optimiste pour une UX fluide
    optimisticResponse: (vars) => ({
      __typename: 'Mutation',
      updateListing: {
        __typename: 'Listing',
        id: vars.id,
        ...vars.input,
      },
    }),
  });

  // Mutation de suppression avec mise a jour du cache
  const {
    mutate: deleteMutate,
    loading: deleteLoading,
    onDone: onDeleteDone,
    onError: onDeleteError,
  } = useMutation<DeleteListingResult>(DELETE_LISTING, {
    // Mise a jour du cache pour retirer l'element supprime
    update: (cache, { data }, { variables }) => {
      if (!data?.deleteListing?.success) return;

      const existingData = cache.readQuery({ query: GET_ALL_LISTINGS });

      if (existingData) {
        cache.writeQuery({
          query: GET_ALL_LISTINGS,
          data: {
            listings: existingData.listings.filter(
              (listing) => listing.id !== variables?.id
            ),
          },
        });
      }

      // Eviction complete de l'element du cache
      cache.evict({ id: cache.identify({ __typename: 'Listing', id: variables?.id }) });
      cache.gc();
    },
  });

  // Fonctions wrapper avec gestion d'erreurs
  const createListing = async (input: CreateListingInput) => {
    operationError.value = null;
    try {
      const result = await createMutate({ input });
      return result?.data?.createListing;
    } catch (error) {
      operationError.value = error as Error;
      throw error;
    }
  };

  const updateListing = async (id: string, input: UpdateListingInput) => {
    operationError.value = null;
    try {
      const result = await updateMutate({ id, input });
      return result?.data?.updateListing;
    } catch (error) {
      operationError.value = error as Error;
      throw error;
    }
  };

  const deleteListing = async (id: string) => {
    operationError.value = null;
    try {
      const result = await deleteMutate({ id });
      return result?.data?.deleteListing;
    } catch (error) {
      operationError.value = error as Error;
      throw error;
    }
  };

  return {
    // Mutations
    createListing,
    updateListing,
    deleteListing,
    // Etats de chargement
    createLoading,
    updateLoading,
    deleteLoading,
    // Erreur
    operationError,
    // Callbacks
    onCreateDone,
    onUpdateDone,
    onDeleteDone,
    onCreateError,
    onUpdateError,
    onDeleteError,
  };
}

Gestion avancee du cache

Strategies de mise a jour du cache

StrategieDescriptionCas d’usage
updateMise a jour manuelle du cacheAjout/suppression d’elements
refetchQueriesRe-execution de requetesDonnees complexes a synchroniser
optimisticResponseReponse anticipeeUX instantanee
cache.evictSuppression d’entiteNettoyage apres suppression
cache.modifyModification directeMises a jour partielles

Exemple avec refetchQueries

const { mutate } = useMutation(CREATE_LISTING, {
  // Re-execute ces requetes apres la mutation
  refetchQueries: [
    { query: GET_ALL_LISTINGS },
    { query: GET_LISTINGS_COUNT },
    'GetUserListings', // Par nom de requete
  ],
  // Attend que les refetch soient termines
  awaitRefetchQueries: true,
});

Exemple avec cache.modify

const { mutate } = useMutation(UPDATE_LISTING_STATUS, {
  update: (cache, { data }) => {
    // Modification directe sans re-lecture
    cache.modify({
      id: cache.identify({ __typename: 'Listing', id: data.updateStatus.id }),
      fields: {
        status: () => data.updateStatus.status,
        updatedAt: () => new Date().toISOString(),
      },
    });
  },
});

Bonnes Pratiques

1. Toujours typer vos operations GraphQL

// Utilisez des interfaces TypeScript ou generez les types automatiquement
import { useQuery } from '@vue/apollo-composable';
import type { GetListingsQuery, GetListingsQueryVariables } from '@/generated/graphql';

const { result } = useQuery<GetListingsQuery, GetListingsQueryVariables>(
  GET_LISTINGS,
  { first: 10 }
);

2. Centraliser la logique dans des composables

// Creez des composables reutilisables plutot que d'appeler useQuery dans chaque composant
export function useListing(id: Ref<string>) {
  return useQuery(GET_LISTING_BY_ID, () => ({ id: id.value }));
}

3. Implementer une gestion d’erreurs coherente

// src/composables/useGraphQLError.ts
import { ref, watch } from 'vue';
import { ApolloError } from '@apollo/client/errors';

export function useGraphQLError(error: Ref<ApolloError | null>) {
  const userMessage = ref<string>('');

  watch(error, (newError) => {
    if (!newError) {
      userMessage.value = '';
      return;
    }

    // Traduction des erreurs techniques en messages utilisateur
    if (newError.networkError) {
      userMessage.value = 'Probleme de connexion. Verifiez votre connexion internet.';
    } else if (newError.graphQLErrors?.length) {
      const firstError = newError.graphQLErrors[0];
      switch (firstError.extensions?.code) {
        case 'UNAUTHENTICATED':
          userMessage.value = 'Votre session a expire. Veuillez vous reconnecter.';
          break;
        case 'FORBIDDEN':
          userMessage.value = 'Vous n\'avez pas les droits pour cette action.';
          break;
        case 'NOT_FOUND':
          userMessage.value = 'L\'element demande n\'existe pas.';
          break;
        default:
          userMessage.value = firstError.message;
      }
    }
  });

  return { userMessage };
}

4. Utiliser les fragments pour la reutilisation

// Les fragments evitent la duplication et garantissent la coherence
const LISTING_CARD_FRAGMENT = gql`
  fragment ListingCard on Listing {
    id
    title
    price
    thumbnail
  }
`;

// Reutilisez dans plusieurs requetes
const GET_FEATURED = gql`
  query GetFeatured {
    featured { ...ListingCard }
  }
  ${LISTING_CARD_FRAGMENT}
`;

5. Preferer cache-and-network pour les donnees critiques

// Affiche les donnees cachees immediatement puis rafraichit en arriere-plan
const { result } = useQuery(GET_LISTINGS, null, {
  fetchPolicy: 'cache-and-network',
});

6. Implementer des optimistic updates pour les mutations

// L'UI se met a jour instantanement sans attendre le serveur
const { mutate } = useMutation(TOGGLE_FAVORITE, {
  optimisticResponse: ({ id, isFavorite }) => ({
    __typename: 'Mutation',
    toggleFavorite: {
      __typename: 'Listing',
      id,
      isFavorite: !isFavorite,
    },
  }),
});

Pieges Courants

1. Oublier la reactivite des variables

// MAUVAIS - Les variables ne sont pas reactives
const { result } = useQuery(GET_LISTING, { id: props.id });

// BON - Utiliser une fonction pour la reactivite
const { result } = useQuery(GET_LISTING, () => ({ id: props.id }));

2. Ne pas gerer l’etat initial du result

// MAUVAIS - Peut causer une erreur si result est undefined
const title = result.value.listing.title;

// BON - Utiliser le chainage optionnel et une valeur par defaut
const title = computed(() => result.value?.listing?.title ?? '');

// ENCORE MIEUX - Utiliser useResult
const title = useResult(result, '', data => data.listing.title);

3. Mises a jour de cache incorrectes apres suppression

// MAUVAIS - Oubli de l'eviction complete
update: (cache, { data }, { variables }) => {
  // L'element reste en cache meme s'il est retire de la liste
};

// BON - Eviction complete de l'entite
update: (cache, { data }, { variables }) => {
  cache.evict({
    id: cache.identify({ __typename: 'Listing', id: variables.id })
  });
  cache.gc(); // Garbage collection
};

4. Ignorer les erreurs reseau

// MAUVAIS - Aucune gestion d'erreur
const { result } = useQuery(GET_LISTINGS);

// BON - Gestion complete des erreurs
const { result, error, loading } = useQuery(GET_LISTINGS);

watch(error, (newError) => {
  if (newError?.networkError) {
    showToast('Verifiez votre connexion internet');
  }
});

5. Mutation sans feedback utilisateur

// MAUVAIS - L'utilisateur ne sait pas ce qui se passe
await deleteListing({ id });

// BON - Feedback complet
const isDeleting = ref(false);

const handleDelete = async (id: string) => {
  isDeleting.value = true;
  try {
    await deleteListing({ id });
    showToast('Annonce supprimee avec succes');
  } catch (error) {
    showToast('Echec de la suppression', 'error');
  } finally {
    isDeleting.value = false;
  }
};

Tableau comparatif : useQuery vs useMutation

AspectuseQueryuseMutation
ButLecture de donneesEcriture de donnees
ExecutionAutomatique au montageManuelle via mutate()
CacheLecture depuis le cacheMise a jour du cache
ReactiviteVariables reactivesVariables par appel
Refetchrefetch() disponibleNon applicable
OptimisticNon applicableoptimisticResponse
PollingpollInterval optionNon applicable

Conclusion

Vue Apollo Composable offre une integration elegante et puissante entre Vue 3 et GraphQL. Grace a la Composition API, vous beneficiez d’une reactivite complete et d’une organisation du code optimale.

Points cles a retenir

  1. useQuery est le composable principal pour les lectures, avec support complet de la reactivite, du cache et de la pagination.

  2. useMutation gere toutes les operations d’ecriture avec des options puissantes pour la mise a jour du cache et les reponses optimistes.

  3. La gestion du cache est cruciale pour une UX fluide - utilisez update, refetchQueries ou optimisticResponse selon le contexte.

  4. Le typage TypeScript ameliore considerablement la maintenabilite et la detection d’erreurs a la compilation.

  5. L’organisation en composables favorise la reutilisation et la separation des responsabilites.

Prochaines etapes

Pour approfondir vos connaissances, explorez :

  • Les subscriptions GraphQL pour les donnees temps reel
  • La generation automatique de types avec GraphQL Code Generator
  • Les persisted queries pour l’optimisation des performances
  • L’integration avec Pinia pour une gestion d’etat hybride

La documentation officielle de Vue Apollo et d’Apollo Client reste la reference pour les cas d’usage avances et les dernieres fonctionnalites.

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