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.

Klicken Sie auf Upvote
quelle
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 .

chans
quelle
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
Stimme voll und
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 {}
Estradiaz
quelle
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.

ArrayKnight
quelle
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
1

Eine weitere Option ist die Verwendung der Verlaufs-API .

Sobald Sie sich im Ergebnisbildschirm befinden, können Sie den ReplaceState verwenden , um die URL im Verlauf des Browsers zu ersetzen.

Angelo
quelle
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"});
  }
}
Yogesh
quelle
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.

omarjebari
quelle