web-dev-qa-db-fra.com

Données de rendu Vuex extraites de REST API

Pour tel composant 

<template>
    <div>
        <router-link :to="{name:'section', params: { sectionId: firstSectionId }}">Start</router-link>
    </div>
</template>

<script lang="ts">
    import { mapActions } from "vuex"

    export default {
        mounted() {
            this.getSectionId()
        },
        computed: {
            firstSectionId() {
                return this.$store.state.firstSectionId
            }
        },
        methods: mapActions(["getSectionId"])
    }
</script>

Le magasin:

const store: any = new Vuex.Store({
    state: {
        firstSectionId: null
    },
    // actions,
    // mutations
})

J'ai une requête Web dans l'action getSectionId qui récupère les données de manière asynchrone et appelle une mutation qui remplira firstSectionId dans state. Pendant le rendu initial, firstSectionId est null et je reçois l'avertissement qu'un paramètre requis est manquant lors du rendu de router-link.

Ce n'est pas un problème ici d'ajouter v-if="firstSectionId". Mais en général, quelle est l'approche à suivre pour récupérer les données d'un serveur à afficher? Actuellement, tous mes composants vérifient la présence de données dans le magasin avant le rendu. Est-ce normal ou existe-t-il un meilleur moyen d'attendre que les données soient chargées avant de les restituer?

17
Sly

Une approche pour extraire des données de manière asynchrone consiste à utiliser promise dans le magasin de vuex actions.

Vue.http.get(API_URL)
.then((response) => {
  //use response object     
})
.catch((error => {
    console.log(error.statusText)
}))

Pour démontrer que je fais une demande à cette route . Vous pouvez voir à quoi la réponse devrait ressembler. Sauvegardons l'objet de réponse dans le tableau state.users. 

store.js

const store = new Vuex.Store({
  state: {
    users: []
  },  
  mutations: {
    FETCH_USERS(state, users) {
        state.users = users
    }
  },
  actions: {
    fetchUsers({ commit }, { self })  {         
        Vue.http.get("https://jsonplaceholder.typicode.com/users")
        .then((response) => {
            commit("FETCH_USERS", response.body);
            self.filterUsers(); 
        })
        .catch((error => {
            console.log(error.statusText)
        }))
    }
  }
})

export default store

Vous avez remarqué qu'il existe une méthode self.filteruser() après la validation. C'est un moment crucial. Avant cela, nous nous commettons une mutation, qui est une opération synchrone et nous sommes sûrs que nous aurons notre réponse dans store.state qui peut être utilisée dans la méthode filterUsers() de la méthode (n'oubliez pas de transmettre le paramètre self)

Users.vue

import store from "../store/store"

export default {
  name: 'users',
  created() {
    this.$store.dispatch("fetchUsers", { self: this })       
  },
  methods:{
    filterUsers() {
      //do something with users
       console.log("Users--->",this.$store.state.users)       
    }
  }
}

Meilleures façons (ES6 & ES7)

ES6 Promesses pour la programmation asynchrone

//User.vue
created() {
  this.$store.dispatch("fetchUser").then(() => {
    console.log("This would be printed after dispatch!!")
   })
}

//store.js
actions: {
    fetchUser({ commit }) {
        return new Promise((resolve, reject) => {
            Vue.http.get("https://jsonplaceholder.typicode.com/users")
            .then((response) => {
                commit("FETCH_USERS", response.body);
                resolve();
            })
            .catch((error => {
                console.log(error.statusText);
            }));
        });
    }
}

ES7: asynchrone/wait 

Pour vous écarter de l'enfer du rappel et améliorer la programmation asynchrone, utilisez la fonction async et vous pouvez await sur une promesse. Le code semble beaucoup plus facile à suivre (comme s'il est synchrone), mais le code n'est pas lisible par les navigateurs. Vous aurez donc besoin du transpiler Babel pour l'exécuter.

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // wait for actionA to finish
    commit('gotOtherData', await getOtherData())
  }
}
47
t_dom93

D'après mon expérience, vous pouvez ignorer quelques vérifications si vous préréglez l'état avec une valeur vide du même type que le résultat attendu (si vous savez à quoi vous attendre, bien sûr), par exemple. si vous avez un tableau d’éléments, commencez par [] au lieu de null car cela ne cassera pas les directives v-for, les contrôles .length et les tentatives d’accès aux données similaires.

Mais généralement, ajouter v-if est une chose très normale à faire. Il y a une section à ce sujet dans la documentation vue-router et vérifier si des propriétés existent ou non est exactement ce que cela suggère. Une autre solution possible mentionnée consiste à récupérer des données dans beforeRouteEnter guard, ce qui garantit que vous obtiendrez toujours le composant avec vos données déjà disponibles.

En fin de compte, les deux solutions sont correctes et la décision entre elles est davantage une question d’UX/UI.

1
mzgajner

J'avais des exigences similaires pour les lieux et l'API Google Map. J'avais besoin d'extraire mes emplacements de l'API, de les charger dans une liste, puis de les utiliser dans un composant de carte pour créer les marqueurs. J'ai récupéré les données dans une action Vuex avec axios, chargé avec une mutation dans mon état, puis utilisé un getter pour récupérer la matrice résultante dans le crochet cycle de vie monté. Cela a abouti à un tableau vide comme monté monté avant la résolution de l'action asynchrone.

J'ai utilisé store.subscribe pour le résoudre de cette façon:

<template>
  <div class="google-map" :id="mapName"></div>
</template>

<script>
import GoogleMapsLoader from 'google-maps';
import { mapGetters } from 'vuex';

export default {
  name: 'google-map',
  props: ['name'],
  computed: {
    ...mapGetters({
      locations: 'locations/locations',
    }),
  },
  data() {
    return {
      mapName: `${this.name}-map`,
    };
  },
  mounted() {
    this.$store.subscribe((mutation, state) => {      
      if (mutation.type === 'locations/SAVE_LOCATIONS') {
        GoogleMapsLoader.KEY = 'myKey';
        GoogleMapsLoader.load((google) => {
          /* eslint-disable no-new */
          const map = new google.maps.Map(document.getElementById('locations-map'));

          // loop through locations and add markers to map and set map boundaries
          const bounds = new google.maps.LatLngBounds();

          // I access the resulting locations array via state.module.property
          state.locations.locations.forEach((location) => {
            new google.maps.Marker({
              position: {
                lat: location.latitude,
                lng: location.longitude,
              },
              map,
            });
            bounds.extend({
              lat: location.latitude,
              lng: location.longitude,
            });
          });

          map.fitBounds(bounds);
        });
      }
    });
  },
};
0
Nathan Agersea