Come spingere su Vue-router senza aggiungere alla cronologia?


12

Ho la seguente sequenza:

  • Schermo principale

  • Schermata di caricamento

  • Schermata dei risultati

Nella homepage, quando qualcuno fa clic su un pulsante, li invio alla schermata di caricamento, tramite:

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

E una volta terminata l'attività, vengono inviati alla schermata dei risultati tramite

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

Il problema è che di solito vogliono tornare dai risultati alla schermata principale, ma quando fanno clic indietro, vengono inviati di nuovo al caricamento che li riporta ai risultati, quindi sono bloccati in un ciclo infinito e incapaci di andare torna alla schermata principale.

Qualche idea su come risolvere questo problema? Preferirei se ci fosse un'opzione come:

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

che li avrebbe inviati al percorso senza aggiungerlo alla cronologia.


5
this.$router.replace({path: "/results/xxxx"})
Roland Starke,

@RolandStarke Grazie - questo fa funzionare il pulsante Indietro e torna al menu principale, ma perdo il pulsante Avanti e non posso più andare avanti - Qualche idea?
Fai clic su Ottimizza

Il processo è: menu principale, caricamento, risultati. Chiamo $router.replacedal caricamento. Questo ora mi consente di tornare da Risultati -> Principale, ma non riesco quindi ad andare avanti ai risultati.
Fai clic su Ottimizza

Un'altra opzione sarebbe quella di non avere un percorso di caricamento. Spingere invece direttamente sulla route dei risultati che esegue il recupero dei dati durante la creazione, quindi esegue il rendering di una vista di caricamento fino al completamento. Quindi non vi è alcun reinstradamento e la cronologia e il flusso utente dovrebbero rimanere intatti senza $ router.replace.
ArrayKnight

@ClickUpvote hai trovato una soluzione a questo problema ...
cambia il

Risposte:


8

C'è un modo perfetto per gestire questa situazione

È possibile utilizzare la protezione all'interno del componente per controllare il percorso a livello di granuli

Apporta le seguenti modifiche al tuo codice

Nel componente della schermata principale

Aggiungi questo beofreRouteLeave guard nelle opzioni dei componenti, prima di uscire dalla 'schermata dei risultati' stai impostando il percorso da percorrere solo attraverso la schermata di caricamento

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

Nel caricamento del componente dello schermo

Se il percorso torna dal risultato al caricamento, non dovrebbe atterrare qui e saltare direttamente alla schermata principale

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

Nella schermata di caricamento, la guardia beforeRouteEnter NON ha accesso a questo, poiché la guardia viene chiamata prima della conferma della navigazione, quindi il nuovo componente di immissione non è stato ancora creato. Quindi, approfittando di questo, non riceverai le chiamate infinite quando instraderai dalla schermata dei risultati

Nel risultato componente dello schermo

se lo usi torna indietro, non dovrebbe atterrare in caricamento e saltare direttamente alla schermata principale

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

Ho appena creato una piccola applicazione Vue per riprodurre lo stesso problema. Funziona nel mio locale secondo la tua domanda. Spero che risolva anche il tuo problema .


2
Questa soluzione è fragile (introduce il debito delle tecnologie di manutenzione) e richiede voci per ogni possibile percorso per cui potrebbe essere necessaria questa funzionalità.
ArrayKnight

Totalmente d'accordo, non mi piace affatto questa soluzione
omarjebari

2

Immagino router.replacesia la strada da percorrere - ma ancora alcune linee di pensiero (non testate):


Fondamentalmente alla $routermodifica, esegue il rendering del componente di caricamento fino a quando non viene emesso load:stop, quindi esegue il renderingrouter-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 {}

Questo è il tipo di implementazione di cui sto parlando, anche se tendo a implementare il mio componente di caricamento direttamente nei componenti della mia pagina piuttosto che creare un wrapper di route route. Il motivo è: mi piace usare l'approccio scheletro in cui il componente di contenuto viene caricato in uno stato semi-caricato per fornire all'utente un feedback visivo su cosa aspettarsi e quindi sovrapporre il componente di caricamento (se deve bloccare tutte le azioni) o incorporarlo se l'utente può utilizzare l'interfaccia utente durante il caricamento dei dati. Riguarda la percezione della velocità da parte degli utenti e sblocca la loro capacità di fare ciò che vogliono il più rapidamente possibile.
ArrayKnight

@ArrayKnight pensando che si può semplicemente scambiare il componente vista router con il componente corrispondente del percorso e ottenere una sorta di involucro del caricatore - ma sono d'accordo che mettere il caricatore nel percorso sembra un cattivo design
Estradiaz

2

Questa è una chiamata difficile considerando quanto poco sappiamo di ciò che sta accadendo nel tuo percorso di caricamento.

Ma...

Non ho mai avuto la necessità di costruire una rotta di caricamento, caricando solo componenti che vengono renderizzati su più rotte durante la fase di raccolta dati / init.

Un argomento per non avere una rotta di caricamento sarebbe che un utente potrebbe potenzialmente navigare direttamente verso questo URL (per errore) e quindi sembra che non avrebbe abbastanza contesto per sapere dove inviare l'utente o quale azione intraprendere. Anche se questo potrebbe significare che a questo punto cade attraverso un percorso di errore. Nel complesso, non è stata un'ottima esperienza.

Un altro è che se semplifichi i tuoi percorsi, la navigazione tra i percorsi diventa molto più semplice e si comporta come previsto / desiderato senza l'uso di $router.replace.

Capisco che questo non risolve la domanda nel modo in cui stai ponendo. Ma suggerirei di ripensare questo percorso di caricamento.

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')

Conchiglia

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

Pagina principale

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

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

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

Pagina dei risultati

<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 dei risultati

Fondamentalmente lo stesso identico componente di risultati che hai già, ma se loadingè vero, o se resultsè nullo, comunque preferisci, puoi creare un set di dati falso per iterare e creare versioni di scheletri, se lo desideri. Altrimenti, puoi semplicemente mantenere le cose come le hai.


La schermata di caricamento che ho occupa l'intero schermo, quindi non riesco a pensare a un modo per visualizzarlo senza avere il suo percorso. Vedi naminator.io per una demo dal vivo. Aperto a qualsiasi altra idea per farlo.
Fai clic su Ottimizza

Lo guarderò bene al mattino, ma il mio pensiero iniziale è che non c'è motivo per cui non si possa usare la posizione fissa per prendere il controllo dello schermo.
ArrayKnight

Guardando il tuo progetto, sembra che ci siano un paio di opzioni: potresti renderizzare il caricatore al posto del contenuto dei risultati e poi cambiare una volta che i dati sono stati recuperati; oppure puoi sovrapporre il caricatore di uno scheletro dei tuoi risultati, quindi aggiornare e popolare i risultati e rimuovere il caricatore. Considerando che il caricatore non copre l'intestazione (shell del sito) non dovresti aver bisogno di correggere la posizione.
ArrayKnight,

@ClickUpvote fammi sapere come ti senti riguardo questo approccio.
ArrayKnight

@ ArrayKnight, la domanda è che i percorsi sono già implementati e funziona come previsto, questo approccio di caricamento potrebbe non adattarsi qui, ho attraversato questa stessa situazione. Il percorso di caricamento può anche avere una richiesta post modulo come gateway di pagamento o alcune azioni post modulo. In questo scenario non possiamo comprendere la rimozione di una rotta. Ma ancora a livello di router di Vue possiamo avere prima di entrare in guardia, perché ho suggerito questo approccio, perché l'istanza di componente di caricamento non è ancora stata creata con questo hook ed è il vantaggio di decidere se entrare in questa route o meno in base alla route precedente
chans

1

Un'altra opzione è utilizzare l' API Cronologia .

Una volta che ci si trova nella schermata Risultati, è possibile utilizzare ReplaceState per sostituire l'URL nella cronologia del browser.


0

Questo può essere fatto con il beforeEachgancio del router.

Quello che devi fare è salvare una variabile a livello globale o in localStorage nel loadingcomponente quando i dati vengono caricati (prima di reindirizzare al resultscomponente):

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

E quindi dovresti controllare questa variabile nel gancio BeforeEach e saltare al componente corretto:

// 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();
});

Inoltre, ricorda di reimpostare il valore su false nel maincomponente quando fai clic sul pulsante di query (prima di instradare al loadingcomponente):

export default {
  name: "main",
  ...
  queryButtonClicked() {
    localStorage.setItem('DATA_LOADED', JSON.stringify(false));
    this.$router.push({path: "/loading"});
  }
}

Questa soluzione è fragile (introduce il debito delle tecnologie di manutenzione) e richiede voci per ogni possibile percorso per cui potrebbe essere necessaria questa funzionalità.
ArrayKnight

0

La schermata di caricamento non dovrebbe essere controllata dal vue-router. L'opzione migliore è utilizzare un modale / overlay per la schermata di caricamento, controllata da javascript. Ci sono molti esempi in giro per Vue. Se non riesci a trovare nulla, vue-bootstrap ha alcuni semplici esempi da implementare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.