Como enviar para o vue-router sem adicionar ao histórico?

12

Eu tenho a seguinte sequência acontecendo:

  • Tela principal

  • Tela de carregamento

  • Tela de resultados

Na página inicial, quando alguém clica em um botão, eu os envio para a tela de carregamento, através de:

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

E quando a tarefa termina, eles são enviados para a tela de resultados via

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

O problema é que, geralmente, eles querem ir dos resultados para a tela principal, mas quando clicam, são enviados para o carregamento novamente, o que os envia de volta aos resultados, para que fiquem presos em um loop infinito e incapazes de ir de volta à tela principal.

Alguma idéia de como corrigir isso? Idealmente, eu gostaria que houvesse uma opção como:

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

que os enviaria para a rota sem adicioná-la ao histórico.

Click Voto a favor
fonte
5
this.$router.replace({path: "/results/xxxx"})
Roland Starke
@RolandStarke Obrigado - isso faz o botão voltar funcionar e voltar ao menu principal, mas perco o botão avançar e não consigo avançar novamente - alguma idéia?
Click Voto a favor
O processo é: Menu principal, Carregamento, Resultados. Estou ligando $router.replacede Loading. Isso agora me permite voltar de Resultados -> Principal, mas não consigo avançar para os resultados.
Click Voto a favor
Outra opção seria não ter uma rota de carregamento. Em vez disso, envie diretamente para a rota de resultados que os dados buscam ao criar e, em seguida, renderiza uma exibição de carregamento até que seja concluída. Então não há redirecionamento e histórico e o fluxo do usuário deve permanecer intacto sem o $ router.replace.
ArrayKnight 18/10/19
@ClickUpvote você encontrou qualquer solução para este problema ...
Chans

Respostas:

8

Existe uma maneira perfeita de lidar com essa situação

Você pode usar a proteção no componente para controlar a rota no nível de grânulos

Faça as seguintes alterações no seu código

No componente da tela principal

Adicione essa proteção beofreRouteLeave nas opções de componentes. Antes de sair para a 'tela de resultado', você está configurando a rota para passar apenas pela tela de carregamento

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

No carregamento do componente da tela

Se a rota voltar do resultado para o carregamento, ela não deve pousar aqui e ir diretamente para a tela principal

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

Na tela de carregamento, o guarda beforeRouteEnter NÃO tem acesso a isso, porque o guarda é chamado antes da confirmação da navegação, portanto, o novo componente de entrada ainda nem foi criado. Portanto, tirando vantagem disso, você não receberá as chamadas infinitas ao rotear da tela de resultados

No componente da tela de resultado

se você usar voltar, não deve parar no carregamento e pular diretamente para a tela principal

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

Acabei de criar um pequeno aplicativo vue para reproduzir o mesmo problema. Funciona no meu local conforme sua pergunta. Espero que ele resolva seu problema também.

chans
fonte
2
Esta solução é frágil (apresenta dívidas técnicas de manutenção) e requer entradas para todas as rotas possíveis para as quais você pode precisar dessa funcionalidade.
ArrayKnight 20/10/19
Concordo totalmente, eu não gosto desta solução em tudo
omarjebari 8/04
2

Eu acho que router.replaceé o caminho a percorrer - mas ainda algumas linhas de pensamento (não testadas):


Basicamente, na $routermudança, ele renderiza o componente de carregamento até que seja emitido load:stop;router-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
fonte
Estou falando desse tipo de implementação, embora eu tendem a implementar meu componente de carregamento diretamente nos componentes da minha página, em vez de criar um wrapper de observação de rota. O motivo é: eu gosto de usar a abordagem esquelética em que o componente de conteúdo é carregado em um estado semi-carregado para fornecer ao usuário um feedback visual do que esperar e, em seguida, sobrepor o componente de carregamento (se precisar bloquear todas as ações) ou incorporá-lo se o usuário pode usar a interface do usuário enquanto os dados são carregados. É uma questão de percepção do usuário sobre velocidade e desbloqueio da capacidade de fazer o que quer o mais rápido possível.
ArrayKnight
@ArrayKnight pensar um resumo pode simplesmente trocar o componente router-view com a sua componente de rota coresponding e recebe algum tipo de invólucro loader - mas concordo colocar o carregador na rota sente como design ruim
Estradiaz
2

Essa é uma decisão difícil, considerando o pouco que sabemos sobre o que está ocorrendo em sua rota de carregamento.

Mas...

Eu nunca tive a necessidade de criar uma rota de carregamento, apenas carregando componentes que são renderizados em várias rotas durante o estágio de coleta de dados / init.

Um argumento para não ter uma rota de carregamento seria que um usuário poderia navegar diretamente diretamente para esse URL (acidentalmente) e, em seguida, parece que não teria contexto suficiente para saber para onde enviar o usuário ou que ação tomar. Embora isso possa significar que ele cai em uma rota de erro neste momento. No geral, não é uma ótima experiência.

Outra é que, se você simplificar suas rotas, a navegação entre rotas se torna muito mais simples e se comporta como esperado / desejado sem o uso de $router.replace.

Entendo que isso não resolve a questão da maneira que você está perguntando. Mas eu sugiro repensar essa rota de carregamento.

Aplicativo

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

Concha

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

Página 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 Resultados

Basicamente, exatamente o mesmo componente de resultados que você já possui, mas se loadingfor verdadeiro ou resultsnulo, como preferir, você pode criar um conjunto de dados falso para iterar e criar versões de esqueleto, se desejar. Caso contrário, você pode simplesmente manter as coisas do jeito que está.

ArrayKnight
fonte
A tela de carregamento que tenho ocupa a tela inteira, então não consigo pensar em uma maneira de exibi-la sem ter sua própria rota. Veja naminator.io para uma demonstração ao vivo. Aberto a outras idéias para fazer isso.
Click Voto a favor
Vou dar uma olhada adequada de manhã, mas meu pensamento inicial é que não há razão para que você não possa usar a posição fixa para assumir o controle da tela.
ArrayKnight 21/10/19
Olhando para o seu design, parece que existem algumas opções: você pode renderizar o carregador no lugar do conteúdo dos resultados e alternar assim que os dados forem buscados; ou você pode sobrepor o carregador de um esqueleto de seus resultados, atualizar e preencher os resultados e remover o carregador. Considerando que o carregador não cobre o cabeçalho (shell do site), você não deve precisar de uma posição fixa.
ArrayKnight 21/10/19
@ClickUpvote, deixe-me saber como você se sente sobre essa abordagem.
ArrayKnight 22/10/19
@ ArrayKnight, a questão é que as rotas já estão implementadas e está funcionando como esperado, essa abordagem de carregamento pode não se encaixar aqui, já passei por essa mesma situação. A rota de carregamento também pode ter uma solicitação de postagem de formulário, como gateway de pagamento ou algumas ações de postagem de formulário. Nesse cenário, não podemos incluir a remoção de uma rota. Mas, ainda no nível do roteador vue, podemos ter antes de entrar nos protetores. Por que sugeri essa abordagem, a instância do vue do componente de carregamento ainda não foi criada nesse gancho e é a vantagem de decidir se deve entrar nessa rota ou não com base na rota anterior
chans
1

Outra opção é usar a API do histórico .

Quando estiver na tela Resultados, você poderá utilizar o ReplaceState para substituir o URL no histórico do navegador.

Angelo
fonte
0

Isso pode ser feito com o beforeEachgancho do roteador.

O que você precisa fazer é salvar uma variável globalmente ou em localStorage no loadingcomponente quando os dados são carregados (antes de redirecionar para o resultscomponente):

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

E você deve verificar essa variável no gancho beforeEach e pular para o componente correto:

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

Além disso, lembre-se de redefinir o valor para false no seu maincomponente quando o botão de consulta for clicado (antes de rotear para o loadingcomponente):

export default {
  name: "main",
  ...
  queryButtonClicked() {
    localStorage.setItem('DATA_LOADED', JSON.stringify(false));
    this.$router.push({path: "/loading"});
  }
}
Yogesh
fonte
Esta solução é frágil (apresenta dívidas técnicas de manutenção) e requer entradas para todas as rotas possíveis para as quais você pode precisar dessa funcionalidade.
ArrayKnight 20/10/19
0

Sua tela de carregamento não deve ser controlada pelo vue-router. A melhor opção é usar uma sobreposição / modal para sua tela de carregamento, controlada por javascript. Existem muitos exemplos para o vue. Se você não consegue encontrar nada, o vue-bootstrap tem alguns exemplos fáceis de implementar.

omarjebari
fonte