¿Cómo empujar a vue-router sin agregar al historial?

12

Tengo la siguiente secuencia sucediendo:

  • Pantalla principal

  • Cargando pantalla

  • Pantalla de resultados

En la página de inicio, cuando alguien hace clic en un botón, lo envío a la pantalla de carga, a través de:

this.$router.push({path: "/loading"});

Y una vez que finaliza su tarea, se envían a la pantalla de resultados a través de

this.$router.push({path: "/results/xxxx"});

El problema es que, por lo general, quieren volver de los resultados a la pantalla principal, pero cuando vuelven a hacer clic, se vuelven a cargar, lo que los devuelve a los resultados, por lo que están atrapados en un bucle infinito y no pueden ir de vuelta a la pantalla principal.

Alguna idea de cómo solucionar este problema? Idealmente me gustaría si hubiera una opción como:

this.$router.push({path: "/loading", addToHistory: false});

que los enviaría a la ruta sin agregarlo al historial.

Haga clic en Upvote
fuente
55
this.$router.replace({path: "/results/xxxx"})
Roland Starke
@RolandStarke Gracias: esto hace que el botón de retroceso funcione y vuelva al menú principal, pero pierdo el botón de avance y no puedo avanzar de nuevo, ¿alguna idea?
Haga clic en Upvote
El proceso es: Menú principal, Carga, Resultados. Estoy llamando $router.replacedesde Loading. Esto ahora me permite volver de Resultados -> Principal, pero luego no puedo avanzar a los resultados.
Haga clic en Upvote
Otra opción sería no tener una ruta de carga. En lugar de eso, empuje directamente a la ruta de resultados que busca los datos al crear, y luego muestra una vista de carga hasta que se complete. Entonces no hay reenrutamiento e historial y el flujo de usuarios debería permanecer intacto sin $ router.replace.
ArrayKnight
@ClickUpvote encontraste alguna solución a este problema ...
Chans

Respuestas:

8

Hay una manera perfecta de manejar esta situación.

Puede usar la protección en componentes para controlar la ruta en el nivel de gránulo

Realice los siguientes cambios en su código

En el componente de pantalla principal

Agregue este protector beofreRouteLeave en las opciones de componentes, antes de salir a la 'pantalla de resultados', está configurando la ruta para ir solo a través de la pantalla de carga

beforeRouteLeave(to, from, next) {
   if (to.path == "/result") {
      next('/loading')
    }
    next();
  }, 

En el componente de la pantalla de carga

Si la ruta retrocede del resultado a la carga, entonces no debe aterrizar aquí y saltar directamente a la pantalla principal

beforeRouteEnter(to, from, next) {
    if (from.path == "/result") {
      next('/main')
    }
     next();
  },

En la pantalla de carga, el protector beforeRouteEnter NO tiene acceso a esto, porque se llama al protector antes de que se confirme la navegación, por lo tanto, el nuevo componente de entrada aún no se ha creado. Entonces, aprovechando esto, no se dispararán las llamadas infinitas al enrutar desde la pantalla de resultados

En resultado componente de pantalla

si usa retroceder, entonces no debería aterrizar en la carga y saltar directamente a la pantalla principal

beforeRouteLeave(to, from, next) {
    if (to.path == "/loading") {
      next('/')
    }
    next();
  },

Acabo de crear una pequeña aplicación vue para reproducir el mismo problema. Funciona en mi local según su pregunta. Espero que resuelva tu problema también.

chans
fuente
2
Esta solución es frágil (presenta una deuda de tecnología de mantenimiento) y requiere entradas para cada ruta posible para la que pueda necesitar esta funcionalidad.
ArrayKnight
Totalmente de acuerdo, no me gusta esta solución en absoluto
omarjebari
2

Supongo que router.replacees el camino a seguir, pero aún algunas líneas de pensamiento (sin probar):


Básicamente, en el $routercambio, representa el componente de carga hasta que emite load:stop, luego representa elrouter-view


import { Vue, Component, Watch, Prop } from "vue-property-decorator";

@Component<RouteLoader>({
    render(h){
        const rv = (this.$slots.default || [])
        .find(
            child => child.componentOptions
            //@ts-ignore 
            && child.componentOptions.Ctor.extendedOptions.name === "RouterView"
        )
        if(rv === undefined) 
            throw new Error("RouterView is missing - add <router-view> to default slot")

        const loader = (this.$slots.default || [])
        .find(
            child => child.componentOptions
            //@ts-ignore 
            && child.componentOptions.Ctor.extendedOptions.name === this.loader
        )
        if(loader === undefined) 
            throw new Error("LoaderView is missing - add <loader-view> to default slot")
        const _vm = this 
        const loaderNode = loader.componentOptions && h(
            loader.componentOptions.Ctor,
            {
                on: {
                    // "load:start": () => this.loading = true,
                    "load:stop": () => _vm.loading = false
                },
                props: loader.componentOptions.propsData,
                //@ts-ignore
                attrs: loader.data.attrs
            }
        )
        return this.loading && loaderNode || rv
    }
})
export default class RouteLoader extends Vue {
    loading: boolean = false
    @Prop({default: "LoaderView"}) readonly loader!: string
    @Watch("$route")
    loads(nRoute: string, oRoute: string){
        this.loading = true
    }
}

@Component<Loader>({
    name: "LoaderView",
    async mounted(){

        await console.log("async call")
        this.$emit("load:stop")
        // this.$destroy()
    }
})
export class Loader extends Vue {}
Estradiaz
fuente
Este es el tipo de implementación de la que estoy hablando, aunque tiendo a implementar mi componente de carga directamente en los componentes de mi página en lugar de crear un contenedor de observación de ruta. La razón es: me gusta usar el enfoque esqueleto donde el componente de contenido se carga en un estado semi cargado para brindarle al usuario retroalimentación visual de qué esperar y luego superponer el componente de carga (si necesita bloquear todas las acciones) o en línea si el usuario puede usar la interfaz de usuario mientras se cargan los datos. Se trata de que el usuario perciba la velocidad y desbloquee su capacidad de hacer lo que quiera lo más rápido posible.
ArrayKnight
@ArrayKnight pensando en abstracto, uno simplemente puede intercambiar el componente de vista de enrutador con su componente de ruta correspondiente y obtiene algún tipo de envoltura del cargador, pero estoy de acuerdo en que poner el cargador en la ruta se siente como un mal diseño
Estradiaz
2

Esta es una decisión difícil considerando lo poco que sabemos sobre lo que ocurre en su ruta de carga.

Pero...

Nunca he tenido la necesidad de construir una ruta de carga, solo cargando los componentes que se procesan en varias rutas durante la etapa de inicio / recopilación de datos.

Un argumento para no tener una ruta de carga sería que un usuario podría navegar directamente a esta URL (accidentalmente) y luego parece que no tendría suficiente contexto para saber dónde enviar al usuario o qué acción tomar. Aunque esto podría significar que cae en una ruta de error en este momento. En general, no fue una gran experiencia.

Otra es que si simplifica sus rutas, la navegación entre rutas se vuelve mucho más simple y se comporta como se esperaba / desea sin el uso de $router.replace .

Entiendo que esto no resuelve la pregunta en la forma en que la preguntas. Pero sugeriría repensar esta ruta de carga.

App

<shell>
    <router-view></router-view>
</shell>

const routes = [
  { path: '/', component: Main },
  { path: '/results', component: Results }
]

const router = new VueRouter({
  routes,
})

const app = new Vue({
  router
}).$mount('#app')

Cáscara

<div>
    <header>
        <nav>...</nav>
    </header>
    <main>
        <slot></slot>
    </main>
</div>

Pagina principal

<section>
    <form>...</form>
</section>

{
    methods: {
        onSubmit() {
            // ...

            this.$router.push('/results')
        }
    }
}

Página de resultados

<section>
    <error v-if="error" :error="error" />
    <results v-if="!error" :loading="loading" :results="results" />
    <loading v-if="loading" :percentage="loadingPercentage" />
</section>

{
    components: {
        error: Error,
        results: Results,
    },
    data() {
        return {
            loading: false,
            error: null,
            results: null,
        }
    },
    created () {
        this.fetchData()
    },
    watch: {
        '$route': 'fetchData'
    },
    methods: {
        fetchData () {
            this.error = this.results = null
            this.loading = true

            getResults((err, results) => {
                this.loading = false

                if (err) {
                    this.error = err.toString()
                } else {
                    this.results = results
                }
            })
        }
    }
}

Componente de resultados

Básicamente, el mismo componente de resultados exactos que ya tiene, pero si loadinges verdadero o si resultses nulo, como prefiera, puede crear un conjunto de datos falso para iterar y crear versiones de esqueleto, si lo desea. De lo contrario, puede mantener las cosas como las tiene.

ArrayKnight
fuente
La pantalla de carga que tengo ocupa toda la pantalla, por lo que no puedo pensar en una forma de mostrarla sin tener su propia ruta. Vea naminator.io para una demostración en vivo. Abierto a cualquier otra idea para hacer esto.
Haga clic en Upvote
Le daré una mirada adecuada por la mañana, pero mi pensamiento inicial es que no hay razón para que no pueda usar la posición fija para hacerse cargo de la pantalla.
ArrayKnight
Al observar su diseño, parece que hay un par de opciones: podría representar el cargador en lugar del contenido de resultados y luego cambiar una vez que se hayan obtenido los datos; o puede superponer el cargador de un esqueleto de sus resultados y luego actualizar y completar los resultados y eliminar el cargador. Teniendo en cuenta que el cargador no cubre el encabezado (shell del sitio), no debería necesitar una posición fija.
ArrayKnight
@ClickUpvote déjame saber cómo te sientes acerca de este enfoque.
ArrayKnight
@ ArrayKnight, la pregunta es si las rutas ya están implementadas y funciona como se esperaba, este enfoque de carga puede no encajar aquí, he pasado por esta misma situación. La ruta de carga también puede tener una solicitud de envío de formulario como pasarela de pago o algunas acciones de envío de formulario. En este escenario, no podemos comprender eliminar una ruta. Pero aún en el nivel de enrutador vue podemos tener antes de ingresar guardias. Por qué sugerí este enfoque, vue la instancia del componente de carga aún no se ha creado en este gancho y es la ventaja de decidir si ingresar a esta ruta o no según la ruta anterior
chans
1

Otra opción es usar la API de historial .

Una vez que esté en la pantalla de Resultados, puede utilizar el Reemplazar Estado para reemplazar la URL en el historial del navegador.

Angelo
fuente
0

Esto se puede hacer con el beforeEachgancho del enrutador.

Lo que debe hacer es guardar una variable globalmente o en localStorage en el loadingcomponente cuando se cargan los datos (antes de redirigir al resultscomponente):

export default {
  name: "results",
  ...
  importantTask() {
    // do the important work...
    localStorage.setItem('DATA_LOADED', JSON.stringify(true));
    this.$router.push({path: "/results/xxxx"});
  }
}

Y luego debe verificar esta variable en el gancho beforeEach y saltar al componente correcto:

// router.js...
router.beforeEach((to, from, next) => {
  const dataLoaded = JSON.parse(localStorage.getItem('DATA_LOADED'));
  if (to.name === "loading" && dataLoaded)
  {
    if (from.name === "results")
    {
      next({ name: "main"} );
    }
    if (from.name === "main")
    {
      next({ name: "results"} );
    }
  }
  next();
});

Además, recuerde restablecer el valor a falso en su maincomponente cuando se hace clic en el botón de consulta (antes de enrutar al loadingcomponente):

export default {
  name: "main",
  ...
  queryButtonClicked() {
    localStorage.setItem('DATA_LOADED', JSON.stringify(false));
    this.$router.push({path: "/loading"});
  }
}
Yogesh
fuente
Esta solución es frágil (presenta una deuda de tecnología de mantenimiento) y requiere entradas para cada ruta posible para la que pueda necesitar esta funcionalidad.
ArrayKnight
0

Vue-router no debe controlar su pantalla de carga. La mejor opción es usar un modal / overlay para su pantalla de carga, controlado por javascript. Hay muchos ejemplos para vue. Si no puede encontrar nada, vue-bootstrap tiene algunos ejemplos fáciles de implementar.

omarjebari
fuente