# Ătat local
# Pourquoi utiliser Apollo comme gestionnaire d'Ă©tat local?
Quand vous faites des requĂȘtes GraphQL abec Apollo, les rĂ©sultats sont stockĂ©s dans le cache Apollo. Maintenant, imaginez que vous ayez Ă©galement besoin d'un Ă©tat applicatif local et de le mettre Ă disposition de plusieurs composants. Normalement, dans une application Vue, nous utilisons Vuex (opens new window) pour ça. Mais utiliser Apollo et Vuex en mĂȘme temps implique de stocker la donnĂ©e Ă deux endroits diffĂ©rents, ce qui donne lieu Ă deux sources de vĂ©ritĂ©.
La bonne nouvelle, c'est qu'Apollo a un mécanisme pour stocker l'état applicatif local en cache. Auparavant, il utilisait la bibliothÚque apollo-link-state (opens new window) pour cela. Depuis la sortie d'Apollo 2.5, cette fonctionnalité est inclue dans Apollo.
# Créer un schéma local
Tout comme créer un schéma GraphQL est la premiÚre étape pour définir un modÚle de données sur le serveur, écrire un schéma local est la premiÚre étape cÎté client.
Créons donc un schéma local pour décrire le premier élément d'une liste de tùches à accomplir ("todo"). Cette tùche comporte du texte, une propriété qui détermine si ell est déja achevée, et un identifiant pour distinguer les tùches entre elles. On la représente donc sous la forme d'un objet avec trois propriétés :
{
id: 'identifiantUnique',
text: 'Du texte',
done: false
}
Nous sommes maintenant prĂȘts Ă ajouter un type Item
à notre schéma GraphQL.
// main.js
import gql from 'graphql-tag';
export const typeDefs = gql`
type Item {
id: ID!
text: String!
done: Boolean!
}
`;
qgl
est un gabarit Ă©tiquetĂ© qui analyse les requĂȘtes GraphQL.
Nous devons maintenant ajouter typeDefs
Ă notre client Apollo.
// main.js
const apolloClient = new ApolloClient({
typeDefs,
resolvers: {},
});
WARNING
Comme vous pouvez le constater, nous avons également ajouté un objet resolvers
vide : si nous oublions de l'ajouter dans les options du client Apollo, il ne pourra pas reconnaĂźtre les requĂȘtes vers l'Ă©tat local et tentera de les envoyer Ă des URLs distants Ă la place.
# Ătendre un schĂ©ma GraphQL distant localement
Vous pouvez non seulement créet un schéma local à partir de zéro, mais aussi ajouter des champs virtuels locaux à votre schéma distant. Ces champs existent uniquement cÎté client, et sont parfaits pour injecter de l'état local à votre donnée serveur.
Imaginez que nous ayons un type User
dans notre schéma distant :
type User {
name: String!
age: Int!
}
Et que nous souhaitions ajouter une propriété locale à User
:
export const schema = gql`
extend type User {
twitter: String
}
`;
Maintenant, quand vous requĂȘtez un utilisateur, il vous faudra spĂ©cifier que le champ twitter
est local :
const userQuery = gql`
user {
name
age
twitter @client
}
`;
# Initialiser un cache Apollo
Pour initialiser un cach Apollo dans votre application, vous devez utiliser un constructeur InMemoryCache
. Tout d'abord, importez-le dans votre fichier principal :
// main.js
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
const cache = new InMemoryCache();
Nous pouvons maintenant l'ajouter aux options de notre client Apollo :
// main.js
const apolloClient = new ApolloClient({
cache,
typeDefs,
resolvers: {},
});
Pour l'instant, le cache est vide. Pour y ajouter des données initiales, nous deevons utiliser la méthode writeData
:
// main.js
const apolloClient = new ApolloClient({
cache,
typeDefs,
resolvers: {},
});
cache.writeData({
data: {
todoItems: [
{
__typename: 'Item',
id: 'dqdBHJGgjgjg',
text: 'test',
done: true,
},
],
},
});
Nous venons d'ajouter un tableau de todoItems
à notre cache et nous avons déterminé que chaque élément a un __typename
nommé Item
(spécifié dans notre schéma local).
# RequĂȘter de la donnĂ©e locale
RequĂȘter le cache local est similaire Ă envoyer des requĂȘtes GraphQL Ă un serveur distant. D'abord, nous devons crĂ©er une requĂȘte :
// App.vue
import gql from 'graphql-tag';
const todoItemsQuery = gql`
{
todoItems @client {
id
text
done
}
}
`;
La diffĂ©rence principale avec des requĂȘtes distantes est la directive @client
. Elle spĂ©cifie que cette requĂȘte ne doit pas ĂȘtre exĂ©cutĂ©e vers l'API GraphQL distante. Ă la place, le client Apollo doit rĂ©cupĂ©rer les rĂ©sultats depuis le cache local.
Nous pouvons maintenant utiliser cette requĂȘte dans notre composant Vue comme n'import quelle requĂȘte Apollo :
// App.vue
apollo: {
todoItems: {
query: todoItemsQuery
}
},
# Changer de la donnée locale avec des mutations
Il existe deux façons différentes de modifier la donnée locale :
- l'écrire directement avec la méthode
writeData
comme nous l'avons fait lors de l'initialisation du cache; - invoquer une mutation GraphQL.
Ajoutons quelques mutations à notre schéma GraphQL local :
// main.js
export const typeDefs = gql`
type Item {
id: ID!
text: String!
done: Boolean!
}
type Mutation {
checkItem(id: ID!): Boolean
addItem(text: String!): Item
}
`;
La mutation checkItem
inversera la propriété booléenne done
d'un élément donné. Créons-la en utilisant gql
:
// App.vue
const checkItemMutation = gql`
mutation($id: ID!) {
checkItem(id: $id) @client
}
`;
Nous avons défini une mutation locale (car nous utilisons la directive @client
) qui accepte un identifiant unique en paramÚtre. Maintenant, il nous faut un résolveur: une fonction qui résout une valeur pour un type ou un champ dans un schéma.
Dans notre cas, le rĂ©solveur dĂ©finit les changements que nous souhaitons apporter Ă notre cache local Apollo quand nous avons certaines mutations. Les rĂ©solveurs locaux ont la mĂȘme signature que les distants ((parent, args, context, info) => data
). En réalité, nous aurons uniquement besoin d'args
(les arguments passés à la mutation) et de context
(nous aurons besoin de ses propriétés de cache pour lire et écrire de la donnée).
Ajoutons donc un résolveur à notre fichier principal :
// main.js
const resolvers = {
Mutation: {
checkItem: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: todoItemsQuery });
const currentItem = data.todoItems.find(item => item.id === id);
currentItem.done = !currentItem.done;
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
};
Que se passe-t-il ici ?
- on lit
todoItemsQuery
depuis notre cache pour voir quellestodoItems
nous avons; - on cherche un élément qui possÚde un certain identifiant;
- on inverse la propriété
done
de l'élément récupéré; - on écrit nos
todoItems
modifiées en cache; - on retourne la propriété
done
comme résultat de mutation.
Nous devons maintenant remplacer notre objet resolvers
vide avec nos nouveaux resolvers
dans les options du client Apollo :
// main.js
const resolvers = {
Mutation: {
checkItem: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: todoItemsQuery });
const currentItem = data.todoItems.find(item => item.id === id);
currentItem.done = !currentItem.done;
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
};
const apolloClient = new ApolloClient({
cache,
typeDefs,
resolvers,
});
AprĂšs cela, nous pouvons utiliser notre mutation dans notre composant Vue comme n'importe quelle mutation:
// App.vue
methods: {
checkItem(id) {
this.$apollo.mutate({
mutation: checkItemMutation,
variables: { id }
});
},
}
â Rendu cĂŽtĂ© serveur (SSR) Tests â