Table of Contents
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
useQuerypour les requetes de lecture - L’utilisation de
useMutationpour 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 ?
| Caracteristique | Avantage |
|---|---|
| Integration Composition API | Utilisation native des composables Vue 3 |
| Cache intelligent | Normalisation et deduplication automatique des donnees |
| Reactivite complete | Mise a jour automatique de l’UI lors des changements de donnees |
| TypeScript first | Support complet du typage avec generation de types |
| Optimistic UI | Mises a jour instantanees de l’interface avant confirmation serveur |
| Subscriptions | Support 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
| Strategie | Description | Cas d’usage |
|---|---|---|
update | Mise a jour manuelle du cache | Ajout/suppression d’elements |
refetchQueries | Re-execution de requetes | Donnees complexes a synchroniser |
optimisticResponse | Reponse anticipee | UX instantanee |
cache.evict | Suppression d’entite | Nettoyage apres suppression |
cache.modify | Modification directe | Mises 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
| Aspect | useQuery | useMutation |
|---|---|---|
| But | Lecture de donnees | Ecriture de donnees |
| Execution | Automatique au montage | Manuelle via mutate() |
| Cache | Lecture depuis le cache | Mise a jour du cache |
| Reactivite | Variables reactives | Variables par appel |
| Refetch | refetch() disponible | Non applicable |
| Optimistic | Non applicable | optimisticResponse |
| Polling | pollInterval option | Non 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
-
useQuery est le composable principal pour les lectures, avec support complet de la reactivite, du cache et de la pagination.
-
useMutation gere toutes les operations d’ecriture avec des options puissantes pour la mise a jour du cache et les reponses optimistes.
-
La gestion du cache est cruciale pour une UX fluide - utilisez
update,refetchQueriesouoptimisticResponseselon le contexte. -
Le typage TypeScript ameliore considerablement la maintenabilite et la detection d’erreurs a la compilation.
-
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.
In-Article Ad
Dev Mode
Tags
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
Définition de l'état de magasin avec TypeScript et Vue.js
Apprenez à définir et typer l'état de votre store Vue avec TypeScript. Interfaces, génériques reactive() et mutations typées pour un code robuste.
Pinia State Management: The Complete Vue 3 Guide
Master Pinia, the official Vue 3 state management library. Learn stores, actions, getters, plugins, and best practices.
Typage des props Vue.js avec TypeScript et PropType pour un code robuste
Maitrisez le typage explicite des props dans Vue 3 avec TypeScript. Utilisez PropType et les interfaces pour un code type-safe.