Estoy buscando una forma reutilizable de mostrar un cargador de página completa (la barra lateral siempre está visible, pero el cargador debe cubrir la parte del contenido de la página) hasta que se hayan realizado todas las recuperaciones de API necesarias.
Tengo un componente principal LaunchDetails envuelto en un componente PageLoader
LaunchDetails.vue
<template> <PageLoader> <router-link :to="{ name: 'launches' }"> Back to launches </router-link> <h1>{{ name }}</h1> <section> <TabMenu :links="menuLinks" /> </section> <section> <router-view /> </section> </PageLoader> </template> <script> import TabMenu from "@/components/general/TabMenu"; export default { data() { return { menuLinks: [ { to: { name: "launchOverview" }, display_name: "Overview" }, { to: { name: "launchRocket" }, display_name: "Rocket" }, ], }; }, components: { TabMenu, }, created() { this.$store.dispatch("launches/fetchLaunch", this.$route.params.launch_id); }, computed: { name() { return this.$store.getters["launches/name"]; }, }, }; </script> PageLoader.vue
<template> <Spinner v-if="isLoading" full size="medium" /> <slot v-else></slot> </template> <script> import Spinner from "@/components/general/Spinner.vue"; export default { components: { Spinner, }, computed: { isLoading() { return this.$store.getters["loader/isLoading"]; }, }, }; </script> La plantilla LaunchDetails tiene otra vista de enrutador. En estas páginas secundarias, se realizan nuevas solicitudes de recuperación en función de los datos de las solicitudes de LaunchDetails .
RocketDetails.vue
<template> <PageLoader> <h2>Launch rocket details</h2> <RocketCard v-if="rocket" :rocket="rocket" /> </PageLoader> </template> <script> import LaunchService from "@/services/LaunchService"; import RocketCard from "@/components/rocket/RocketCard.vue"; export default { components: { RocketCard, }, mounted() { this.loadRocket(); }, data() { return { rocket: null, }; }, methods: { async loadRocket() { const rocket_id = this.$store.getters["launches/getRocketId"]; if (rocket_id) { const response = await LaunchService.getRocket(rocket_id); this.rocket = response.data; } }, }, }; </script> Lo que necesito es una forma de obtener datos en el componente principal ( LaunchDetails ). Si estos datos se almacenan en la tienda vuex, el componente secundario ( LaunchRocket ) obtiene los datos necesarios de la tienda y ejecuta las solicitudes de recuperación. Mientras se hace esto, me gustaría tener un cargador de página completa o un cargador de página completa mientras se carga el componente principal y un cargador que contenga el lienzo anidado.
En este punto, la tienda vuex realiza un seguimiento de una propiedad isLoading , manejada con interceptores axios.
Todo el código es visible en este sandbox
(Nota: en este ejemplo, podría obtener el rocket_id de la URL, pero este no será el caso en mi proyecto, por lo que realmente estoy buscando una manera de obtener estos datos de la tienda vuex)
Le presento a su salvador Suspense , esta función se ha agregado en vue v3 pero aún es una función experimental. Básicamente, cómo funciona, crea un suspenso en el componente principal y puede mostrar una carga cuando se resuelve todo el componente en cualquier profundidad de su aplicación. Tenga en cuenta que sus componentes deben ser un componente asíncrono, lo que significa que debe cargarse de forma perezosa o hacer que su función de configuración (api de composición) sea una función asíncrona para que devuelva un componente asíncrono, de esta manera puede obtener sus datos en el componente secundario y en el padre mostrar una reserva si es necesario.
Más información:https://v3.vuejs.org/guide/migration/suspense.html#introduction
Podrías usar Eventos:
var Child = Vue.component('child', { data() { return { isLoading: true } }, template: `<div> <span v-if="isLoading">Loading …</span> <span v-else>Child</span> </div>`, created() { this.$parent.$on('loaded', this.setLoaded); }, methods: { setLoaded() { this.isLoading = false } } }); var Parent = Vue.component('parent', { components: { Child }, data() { return { isLoading: true } }, template: `<div> Parent <Child /> </div>`, mounted() { let request1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000); }); let request2 = new Promise((resolve, reject) => { setTimeout(resolve, 2000); }); Promise.all([ request1, request2 ]).then(() => this.$emit('loaded')) } }); new Vue({ components: { Parent }, el: '#app', template: `<Parent />` }); <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div id="app"></div>Esto puede considerarse un antipatrón, ya que une al padre con el hijo y se considera que los eventos se envían al revés. Si no quiere usar eventos para eso, una propiedad observada también funciona bien. La emisión de eventos que no son padre-hijo se eliminó en Vue 3, pero se puede implementar usando bibliotecas externas .