web-dev-qa-db-fra.com

Attendez que la valeur VueX se charge, avant de charger le composant

Lorsqu'un utilisateur essaie de naviguer directement dans le chargement d'une URL de composant, un appel http est effectué dans mes actions vuex, qui définira une valeur dans mon état une fois qu'il sera résolu.

Je ne veux pas charger mon composant tant que l'appel http n'est pas résolu et que la valeur d'état n'est pas définie.

Par exemple, dans mon composant

export default {
  computed: {
    ...mapState({
      // ** this value needs to load before component mounted() runs **
      asyncListValues: state => state.asyncListValues
    })
  },

  mounted () {
    // ** I need asyncListValues to be defined before this runs **
    this.asyncListValues.forEach((val) => { 
      // do stuff
    });
  }
}

Comment puis-je faire en sorte que mon composant attende que asyncListValues se charge, avant de charger mon composant?

3
Edon

La façon de le faire est de stocker les valeurs d'état.

Par exemple, si votre magasin s'appuie sur une seule API, vous feriez quelque chose comme ça. Cependant, pour plusieurs API, c'est une bonne idée de stocker chaque état de chargement de l'API individuellement, ou d'utiliser un objet dédié pour chaque API.

Il y a habituellement 4 états que vous pouvez avoir, que je préfère avoir dans un module accessible mondialement:

// enums.js
export default {
  INIT: 0,
  LOADING: 1,
  ERROR: 2,
  LOADED: 3
};

Ensuite, vous pouvez avoir la variable stockée dans l'état vuex, où l'apiState est initialisé avec INIT. vous pouvez également initialiser le tableau avec [], mais cela ne devrait pas être nécessaire.

import ENUM from "@/enums";
// store.js
export default new Vuex.Store({
  state: {
    apiState: ENUM.INIT,
    accounts: [],
    // ...other state
  },
  mutations: {
    updateAccounts (state, accounts) {
      state.accounts = accounts;
      state.apiState = ENUM.LOADED;
    },
    setApiState (state, apiState) {
      state.apiState = apiState;
    },
  },
  actions: {
    loadAccounts ({commit) {
      commit('setApiState', ENUM.ERROR);
      somFetchInterface()
        .then(data=>commit('updateAccounts', data))
        .catch(err=>commit('setApiState', ENUM.ERROR))
    }
  }
});

Ensuite, en ajoutant des computed, vous pouvez basculer entre les composants affichés. Cela montre l'avantage d'utiliser l'état, car vous pouvez facilement identifier l'état d'erreur et afficher une animation de chargement lorsque l'état n'est pas prêt.

<template>
  <ChildComponent v-if="apiStateLoaded"/>
  <Loader v-if="apiStateLoading"/>
  <Error v-if="apiStateError"/>
</template>
<script>
import ENUM from "@/enums";
export default {
  computed: {
    ...mapState({
      apiState: state=> state.apiState
    }),
    apiStateLoaded() {
      return this.apiState === ENUM.LOADED;
    },
    apiStateLoading() {
      return this.apiState === ENUM.LOADING || this.apiState === ENUM.INIT;
    },
    apiStateError() {
      return this.apiState === ENUM.ERROR;
    },
  })
}
</script>

Alternativement, si vous venez d'initialiser votre asyncListValues dans le magasin avec un tableau vide [], vous pouvez éviter les erreurs qui attendent un tableau.

1
Daniel

Une approche serait de diviser votre composant en deux composants différents. Votre nouveau composant parent pourrait gérer la récupération des données et le rendu du composant enfant une fois que les données sont prêtes.

ParentComponent.vue

<template>
  <child-component v-if="asyncListValues && asyncListValues.length" :asyncListValues="asyncListValues"/>
  <div v-else>Placeholder</div>
</template>
<script>
export default {
  computed: {
    ...mapState({
      asyncListValues: state => state.asyncListValues
    })
  }
}
</script>

ChildComponent.vue

export default {
  props: ["asyncListValues"],
  mounted () {
    this.asyncListValues.forEach((val) => { 
      // do stuff
    });
  }
}
1
Connor

Puisque vous avez mentionné vue-router Dans votre question, vous pouvez utiliser beforeRouteEnter qui est fait pour différer le rendu d'un composant.

Par exemple, si vous avez un itinéraire appelé "photo":

import Photo from "../page/Photo.vue";

new VueRouter({
  mode: "history",
  routes: [
    { name: "home", path: "/", component: Home },
    { name: "photo", path: "/photo", component: Photo }
  ]
});

Vous pouvez utiliser le beforeRouteEnter comme ceci:

<template>
  <div>
    Photo rendered here
  </div>
</template>
<script>
export default {
  beforeRouteEnter: async function(to, from, next) {
    try {
      await this.$store.dispatch("longRuningHttpCall");

      next();
    } catch(exception) {
      next(exception);
    }
  }
}
</script>

Ce qu'il fait, c'est d'attendre que l'action se termine, de mettre à jour votre état comme vous le souhaitez, puis l'appel à next() dira au routeur de continuer le processus (en rendant le composant à l'intérieur du <router-view></router-view>).

Dites-moi si vous avez besoin d'un exemple sans ES6 (si vous n'utilisez pas cette syntaxe par exemple).

Vous pouvez vérifier le documentation officielle de beforeRouteEnter sur cette page, vous découvrirez également que vous pouvez également le mettre au niveau de l'itinéraire en utilisant beforeEnter.

1
Anwar