# É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 ?

  1. on lit todoItemsQuery depuis notre cache pour voir quelles todoItems nous avons;
  2. on cherche un élément qui possÚde un certain identifiant;
  3. on inverse la propriété done de l'élément récupéré;
  4. on écrit nos todoItems modifiées en cache;
  5. 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 }
    });
  },
}
DerniĂšre mise Ă  jour: 11/02/2021, 11:08:30