Comment pousser vers vue-router sans ajouter à l'historique?

12

J'ai la séquence suivante qui se passe:

  • Écran principal

  • Écran de chargement

  • Écran des résultats

Sur la page d'accueil, lorsque quelqu'un clique sur un bouton, je l'envoie à l'écran de chargement, à travers:

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

Et une fois leur tâche terminée, ils sont envoyés à l'écran des résultats via

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

Le problème est, généralement, ils veulent revenir des résultats à l'écran principal, mais quand ils cliquent en arrière, ils sont renvoyés au chargement, ce qui les renvoie aux résultats, donc ils sont coincés dans une boucle infinie et incapables d'aller retour à l'écran principal.

Une idée sur comment réparer ça? J'aimerais idéalement s'il y avait une option comme:

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

ce qui les enverrait sur la route sans l'ajouter à l'histoire.

Cliquez Upvote
la source
5
this.$router.replace({path: "/results/xxxx"})
Roland Starke
@RolandStarke Merci - cela fait fonctionner le bouton Précédent et revenir au menu principal, mais je perds le bouton Suivant et je ne peux plus avancer - des idées?
Cliquez sur Upvote
Le processus est le suivant: menu principal, chargement, résultats. J'appelle $router.replacedepuis Loading. Cela me permet maintenant de revenir de Résultats -> Principal, mais je ne peux plus avancer vers les résultats.
Cliquez sur Upvote
Une autre option serait de ne pas avoir de route de chargement. Poussez plutôt directement vers la route des résultats qui récupère les données lors de la création, puis affiche une vue de chargement jusqu'à ce qu'elle soit terminée. Ensuite, il n'y a pas de réacheminement et l'historique et le flux d'utilisateurs doivent rester intacts sans $ router.replace.
ArrayKnight
@ClickUpvote avez-vous trouvé une solution à ce problème ...
chans

Réponses:

8

Il existe un moyen idéal pour gérer cette situation

Vous pouvez utiliser la protection intégrée pour contrôler l'itinéraire au niveau des granules

Apportez les modifications suivantes dans votre code

Dans le composant de l'écran principal

Ajoutez ce garde beofreRouteLeave dans les options des composants, avant de quitter l'écran de résultat, vous définissez l'itinéraire pour passer uniquement par l'écran de chargement

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

Dans le chargement du composant d'écran

Si l'itinéraire revient du résultat au chargement, il ne devrait pas atterrir ici et passer directement à l'écran principal

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

Dans l'écran de chargement, la garde beforeRouteEnter n'y a PAS accès, car la garde est appelée avant la confirmation de la navigation, donc le nouveau composant entrant n'a même pas encore été créé. Donc, en profitant de cela, vous ne recevrez pas les appels infinis lors du routage à partir de l'écran des résultats

Dans le composant d'écran de résultat

si vous utilisez revenir en arrière, il ne devrait pas atterrir lors du chargement et passer directement à l'écran principal

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

Je viens de créer une application small vue pour reproduire le même problème. Cela fonctionne dans mon local selon votre question. J'espère que cela résoudra également votre problème .

chans
la source
2
Cette solution est fragile (introduit une dette de technologie de maintenance) et nécessite des entrées pour chaque itinéraire possible pour lequel vous pourriez avoir besoin de cette fonctionnalité.
ArrayKnight
Tout à fait d'accord, je n'aime pas du tout cette solution
omarjebari
2

Je suppose que router.replacec'est la voie à suivre - mais encore quelques pistes de réflexion (non testées):


Fondamentalement, lors du $routerchangement, il rend le composant de chargement jusqu'à ce qu'il émette load:stop, puis il rend lerouter-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
la source
C'est le type d'implémentation dont je parle, bien que j'ai tendance à implémenter mon composant de chargement directement dans mes composants de page plutôt que de créer un wrapper de surveillance d'itinéraire. La raison en est: j'aime utiliser l'approche squelette où le composant de contenu se charge dans un état semi-chargé pour donner à l'utilisateur un retour visuel de ce à quoi s'attendre, puis superposer le composant de chargement (s'il doit bloquer toutes les actions) ou l'intégrer si l'utilisateur peut utiliser l'interface utilisateur pendant le chargement des données. Il s'agit avant tout de percevoir la vitesse des utilisateurs et de débloquer leur capacité à faire ce qu'ils veulent le plus rapidement possible.
ArrayKnight
@ArrayKnight en pensant à un résumé, on peut simplement échanger le composant router-view avec son composant de route correspondant et obtenir une sorte de wrapper de chargeur - mais je conviens que le chargement du chargeur dans la route ressemble à une mauvaise conception
Estradiaz
2

Il s'agit d'un appel difficile compte tenu du peu que nous savons sur ce qui se passe dans votre itinéraire de chargement.

Mais...

Je n'ai jamais eu besoin de construire une route de chargement, seulement des composants de chargement qui sont rendus sur plusieurs routes pendant la phase de collecte des données / init.

Un argument pour ne pas avoir de route de chargement serait qu'un utilisateur pourrait potentiellement naviguer directement vers cette URL (accidentellement), puis il semble qu'il n'aurait pas assez de contexte pour savoir où envoyer l'utilisateur ou quelle action prendre. Bien que cela puisse signifier qu'il tombe sur une route d'erreur à ce stade. Dans l'ensemble, ce n'est pas une super expérience.

Un autre est que si vous simplifiez vos itinéraires, la navigation entre les itinéraires devient beaucoup plus simple et se comporte comme prévu / souhaité sans l'utilisation de $router.replace.

Je comprends que cela ne résout pas la question de la manière que vous posez. Mais je suggère de repenser cette route de chargement.

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

coquille

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

Page d'accueil

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

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

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

Page de résultats

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

Composante résultats

Fondamentalement, le même composant de résultats que vous avez déjà, mais si loadingc'est vrai ou si resultsest nul, comme vous préférez, vous pouvez créer un faux ensemble de données pour itérer et créer des versions squelettes, si vous le souhaitez. Sinon, vous pouvez simplement garder les choses telles que vous les avez.

ArrayKnight
la source
L'écran de chargement que j'ai occupe tout l'écran, donc je ne peux pas penser à un moyen de l'afficher sans avoir son propre itinéraire. Voir naminator.io pour une démonstration en direct. Ouvert à toutes autres idées pour ce faire.
Cliquez sur Upvote
Je vais lui donner un bon aperçu le matin, mais ma pensée initiale est qu'il n'y a aucune raison pour laquelle vous ne pouvez pas utiliser la position fixe pour prendre le contrôle de l'écran.
ArrayKnight
En regardant votre conception, il semble qu'il y ait quelques options: vous pouvez rendre le chargeur à la place du contenu des résultats, puis basculer une fois les données récupérées; ou vous pouvez superposer le chargeur d'un squelette de vos résultats, puis mettre à jour et remplir les résultats et supprimer le chargeur. Étant donné que le chargeur ne couvre pas l'en-tête (shell du site), vous ne devriez pas avoir besoin de position fixe.
ArrayKnight
@ClickUpvote, faites-moi savoir ce que vous pensez de cette approche.
ArrayKnight
@ ArrayKnight, la question est que les routes sont déjà implémentées et que cela fonctionne comme prévu, cette approche de chargement peut ne pas convenir ici, j'ai vécu cette même situation. L'itinéraire de chargement peut également avoir une demande de publication de formulaire comme une passerelle de paiement ou certaines actions de publication de formulaire. Dans ce scénario, nous ne pouvons pas comprendre la suppression d'un itinéraire. Mais toujours au niveau du routeur de vue, nous pouvons avoir avant d'entrer dans les gardes.Pourquoi j'ai suggéré que cette approche soit, l'instance de vue du composant de chargement n'est pas encore créée à ce crochet et c'est l'avantage de décider s'il faut entrer dans cette route ou non en fonction de la route précédente
chans
1

Une autre option consiste à utiliser l' API Historique .

Une fois dans l'écran Résultats, vous pouvez utiliser ReplaceState pour remplacer l'URL dans l'historique du navigateur.

Angelo
la source
0

Cela peut être fait avec le beforeEachcrochet du routeur.

Ce que vous devez faire, c'est que vous devez enregistrer une variable globalement ou dans localStorage dans le loadingcomposant lorsque les données sont chargées (avant de rediriger vers le resultscomposant):

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

Et puis vous devez vérifier cette variable dans le crochet beforeEach et passer au composant correct:

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

N'oubliez pas non plus de réinitialiser la valeur à false dans votre maincomposant lorsque vous cliquez sur le bouton de requête (avant le routage vers le loadingcomposant):

export default {
  name: "main",
  ...
  queryButtonClicked() {
    localStorage.setItem('DATA_LOADED', JSON.stringify(false));
    this.$router.push({path: "/loading"});
  }
}
Yogesh
la source
Cette solution est fragile (introduit une dette de technologie de maintenance) et nécessite des entrées pour chaque itinéraire possible pour lequel vous pourriez avoir besoin de cette fonctionnalité.
ArrayKnight
0

Votre écran de chargement ne doit pas du tout être contrôlé par vue-router. La meilleure option est d'utiliser un modal / overlay pour votre écran de chargement, contrôlé par javascript. Il y a beaucoup d'exemples pour la vue. Si vous ne trouvez rien, alors vue-bootstrap a quelques exemples faciles à implémenter.

omarjebari
la source