Wie kann man auf den Vue-Router pushen, ohne den Verlauf zu erweitern?


12

Ich habe die folgende Sequenz passiert:

  • Hauptbildschirm

  • Ladebildschirm

  • Ergebnisbildschirm

Wenn jemand auf der Homepage auf eine Schaltfläche klickt, sende ich ihn an den Ladebildschirm über:

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

Und sobald ihre Aufgabe erledigt ist, werden sie über an den Ergebnisbildschirm gesendet

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

Das Problem ist, dass sie normalerweise von den Ergebnissen zurück zum Hauptbildschirm wechseln möchten. Wenn sie jedoch zurückklicken, werden sie erneut zum Laden gesendet, wodurch sie zu den Ergebnissen zurückkehren, sodass sie in einer Endlosschleife stecken bleiben und nicht mehr gehen können zurück zum Hauptbildschirm.

Irgendwelche Ideen, wie man das behebt? Ich würde im Idealfall gerne eine Option wie:

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

das würde sie zur Route schicken, ohne sie der Geschichte hinzuzufügen.


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

@ RolandStarke Danke - dadurch funktioniert die Zurück-Taste und ich gehe zurück zum Hauptmenü, aber ich verliere die Vorwärts-Taste und kann nicht wieder vorwärts gehen - irgendwelche Ideen?
Klicken Sie auf Upvote

Der Prozess ist: Hauptmenü, Laden, Ergebnisse. Ich rufe $router.replacevom Laden an. Auf diese Weise kann ich jetzt von Ergebnisse -> Haupt zurückkehren, aber dann kann ich nicht zu den Ergebnissen übergehen.
Klicken Sie auf Upvote

Eine andere Möglichkeit wäre, keine Laderoute zu haben. Drücken Sie lieber direkt auf die Ergebnisroute, auf der die Daten beim Erstellen abgerufen werden, und rendern Sie dann eine Ladeansicht, bis sie abgeschlossen ist. Dann gibt es kein Umleiten und der Verlauf und der Benutzerfluss sollte ohne $ router.replace intakt bleiben.
ArrayKnight

@ ClickUpvote haben Sie eine Lösung für dieses Problem gefunden ...
Chans

Antworten:


8

Es gibt eine perfekte Möglichkeit, mit dieser Situation umzugehen

Sie können den komponenteninternen Schutz verwenden , um die Route in Granulat-Ebene zu steuern

Nehmen Sie die folgenden Änderungen in Ihrem Code vor

In der Hauptbildschirmkomponente

Fügen Sie diesen beofreRouteLeave-Schutz in den Komponentenoptionen hinzu, bevor Sie zum Ergebnisbildschirm wechseln, legen Sie die Route so fest, dass nur der Ladebildschirm durchlaufen wird

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

Beim Laden der Bildschirmkomponente

Wenn die Route vom Ergebnis zum Laden zurückgeht, sollte sie nicht hier landen und direkt zum Hauptbildschirm springen

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

Im Ladebildschirm hat der beforeRouteEnter-Guard KEINEN Zugriff darauf, da der Guard vor Bestätigung der Navigation aufgerufen wird und die neu eingegebene Komponente noch nicht einmal erstellt wurde. Wenn Sie dies ausnutzen, werden beim Weiterleiten vom Ergebnisbildschirm nicht die unendlichen Anrufe ausgelöst

In Ergebnisbildschirmkomponente

Wenn Sie go back verwenden, sollte es nicht beim Laden landen und direkt zum Hauptbildschirm springen

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

Ich habe gerade eine kleine Vue-Anwendung erstellt, um das gleiche Problem zu reproduzieren. Es funktioniert in meinem lokalen gemäß Ihrer Frage. Hoffe, es löst auch Ihr Problem .


2
Diese Lösung ist spröde (führt zu technischen Schulden) und erfordert Einträge für jede mögliche Route, für die Sie diese Funktionalität benötigen.
ArrayKnight


2

Ich denke, es router.replaceist der richtige Weg - aber immer noch einige Gedankengänge (ungetestet):


Grundsätzlich wird $routerbeim Laden die Ladekomponente gerendert, bis sie ausgegeben wird load:stop, und dann wird die Ladekomponente gerendertrouter-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 {}

Dies ist die Art von Implementierung, über die ich spreche, obwohl ich meine Ladekomponente eher direkt in meine Seitenkomponenten implementiere, als einen Routenüberwachungs-Wrapper zu erstellen. Der Grund dafür ist: Ich verwende gerne den Skeleton-Ansatz, bei dem die Inhaltskomponente in einen halb geladenen Zustand geladen wird, um dem Benutzer ein visuelles Feedback zu geben, was zu erwarten ist, und überlagert dann die Ladekomponente (wenn alle Aktionen blockiert werden müssen) oder inline, wenn Der Benutzer kann die Benutzeroberfläche verwenden, während Daten geladen werden. Es geht darum, dass Benutzer die Geschwindigkeit wahrnehmen und ihre Fähigkeit, so schnell wie möglich das zu tun, was sie wollen, entsperren.
ArrayKnight

@ArrayKnight denkt abstrakt man kann einfach die Router-View-Komponente mit der entsprechenden Routenkomponente austauschen und bekommt eine Art Loader-Wrapper - aber ich stimme zu, dass es sich nach schlechtem Design anfühlt, den Loader in die Route zu setzen
Estradiaz

2

Dies ist eine schwierige Aufgabe, wenn man bedenkt, wie wenig wir über die Vorgänge auf Ihrer Laderoute wissen.

Aber...

Ich musste noch nie eine Laderoute erstellen, sondern nur Komponenten laden, die während der Init- / Datenerfassungsphase auf mehreren Routen gerendert werden.

Ein Argument für das Fehlen einer Laderoute wäre, dass ein Benutzer möglicherweise (versehentlich) direkt zu dieser URL navigieren könnte und dann nicht genügend Kontext vorhanden wäre, um zu wissen, wohin der Benutzer gesendet werden soll oder welche Maßnahmen er ergreifen soll. Dies könnte jedoch bedeuten, dass es an dieser Stelle zu einer Fehlerroute kommt. Insgesamt keine tolle Erfahrung.

Ein weiterer Grund ist, dass die Navigation zwischen Routen viel einfacher wird und sich wie erwartet / gewünscht verhält, wenn Sie Ihre Routen vereinfachen $router.replace.

Ich verstehe, dass dies die Frage nicht so löst, wie Sie es stellen. Aber ich würde vorschlagen, diese Laderoute zu überdenken.

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

Schale

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

Hauptseite

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

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

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

Ergebnisseite

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

Ergebniskomponente

Grundsätzlich genau die gleiche Ergebniskomponente, die Sie bereits haben, aber wenn dies loadingzutrifft oder wenn resultses null ist, können Sie ein gefälschtes Dataset erstellen, um es zu iterieren und Skelettversionen zu erstellen, wenn Sie möchten. Ansonsten können Sie die Dinge einfach so halten, wie Sie sie haben.


Der Ladebildschirm, den ich habe, nimmt den gesamten Bildschirm ein, daher kann ich mir keine Möglichkeit vorstellen, ihn anzuzeigen, ohne eine eigene Route zu haben. Unter naminator.io finden Sie eine Live-Demo. Offen für andere Ideen dazu.
Klicken Sie auf Upvote

Ich werde es mir am Morgen genauer ansehen, aber mein erster Gedanke ist, dass es keinen Grund gibt, warum Sie die festgelegte Position nicht verwenden könnten, um den Bildschirm zu übernehmen.
ArrayKnight

Wenn Sie sich Ihr Design ansehen, scheint es einige Optionen zu geben: Sie können den Loader anstelle des Ergebnisinhalts rendern und dann wechseln, sobald Daten abgerufen wurden. oder Sie können den Loader eines Skeletts Ihrer Ergebnisse überlagern und dann die Ergebnisse aktualisieren und auffüllen und den Loader entfernen. Da der Loader den Header (Site-Shell) nicht abdeckt, sollte die Position nicht festgelegt werden müssen.
ArrayKnight

@ClickUpvote lassen Sie mich wissen, wie Sie über diesen Ansatz denken.
ArrayKnight

@ ArrayKnight, die Frage ist, dass Routen bereits implementiert sind und wie erwartet funktionieren. Dieser Ladeansatz passt möglicherweise nicht hierher. Ich habe dieselbe Situation durchgemacht. Die Laderoute kann auch eine Formularpostanforderung wie ein Zahlungsgateway oder einige Formularpostaktionen enthalten. In diesem Szenario können wir das Entfernen einer Route nicht berücksichtigen. Aber nach wie vor in vue Router - Ebene können wir vor Wachen betreten haben, warum ich diesen Ansatz vorgeschlagen wird, vue Instanz von Lade Komponente ist noch nicht an diesem Haken erstellt und das der Vorteil , zu entscheiden , ob diese Route eingeben oder nicht , basierend auf vorherige Route
Chans


0

Dies kann mit dem beforeEachHaken des Routers erfolgen.

Sie müssen beim Laden loadingder Daten eine Variable global oder in localStorage in der Komponente speichern (bevor Sie zur resultsKomponente umleiten ):

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

Und dann sollten Sie im beforeEach-Hook nach dieser Variablen suchen und zur richtigen Komponente springen:

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

Denken Sie auch daran, den Wert in Ihrer mainKomponente auf false zurückzusetzen, wenn Sie auf die Abfrageschaltfläche klicken (bevor Sie zur loadingKomponente weiterleiten):

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

Diese Lösung ist spröde (führt zu technischen Schulden) und erfordert Einträge für jede mögliche Route, für die Sie diese Funktionalität benötigen.
ArrayKnight

0

Ihr Ladebildschirm sollte überhaupt nicht vom Vue-Router gesteuert werden. Die beste Option ist die Verwendung eines Modals / Overlays für Ihren Ladebildschirm, das von Javascript gesteuert wird. Es gibt viele Beispiele für Vue. Wenn Sie nichts finden können, hat vue-bootstrap einige einfache Beispiele zu implementieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.