【问题标题】:How to push to vue-router without adding to history?如何在不添加历史记录的情况下推送到 vue-router?
【发布时间】:2020-01-31 17:06:30
【问题描述】:

我有以下顺序发生:

  • 主屏幕

  • 载入画面

  • 结果屏幕

在主页上,当有人点击一个按钮时,我将他们发送到加载屏幕,通过:

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

一旦他们的任务完成,他们就会通过

发送到结果屏幕

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

问题是,通常他们想从结果返回到主屏幕,但是当他们点击返回时,他们会被发送到再次加载,这会将他们发送回结果,所以他们陷入了无限循环 &无法返回主屏幕。

任何想法如何解决这个问题?理想情况下,我希望有这样的选项:

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

这会将它们发送到该路线而不将其添加到历史记录中。

【问题讨论】:

  • this.$router.replace({path: "/results/xxxx"})
  • @RolandStarke 谢谢 - 这使后退按钮工作并返回主菜单,但我失去了前进按钮并且无法再次前进 - 有什么想法吗?
  • 流程为:主菜单、加载、结果。我正在加载中调用$router.replace。现在这让我可以从 Results -> Main 返回,但我无法继续查看结果。
  • 另一种选择是没有装载路线。而是直接推送到在创建时获取数据的结果路由,然后呈现加载视图直到完成。然后没有重新路由,历史和用户流应该在没有 $router.replace 的情况下保持不变。
  • @ClickUpvote 你有没有找到解决这个问题的办法...

标签: javascript vue.js vue-router


【解决方案1】:

这应该有一个真正的答案,使用this.$router.replace

// On login page

// Use 'push' to go to the loading page.
// This will add the login page to the history stack.
this.$router.push({path: "/loading"});

// Wait for tasks to finish

// Use 'replace' to go to the results page.
// This will not add '/loading' to the history stack.
this.$router.replace({path: "/results/xxxx"});

为了进一步阅读,Vue 路由器在幕后使用 History.pushState()History.replaceState()

【讨论】:

    【解决方案2】:

    您的加载屏幕根本不应该由 vue-router 控制。 最好的选择是在加载屏幕上使用模态/覆盖,由 javascript 控制。 vue 有很多例子。如果你找不到任何东西,那么 vue-bootstrap 有一些简单的例子可以实现。

    【讨论】:

      【解决方案3】:

      另一种选择是使用History API

      进入结果屏幕后,您可以使用 ReplaceState 替换浏览器历史记录中的 URL。

      【讨论】:

        【解决方案4】:

        考虑到我们对您的装载路线中发生的事情知之甚少,这是一个艰难的决定。

        但是……

        我从来不需要构建加载路径,只需要加载在初始化/数据收集阶段在多个路径上呈现的组件。

        没有加载路径的一个论据是,用户可能会直接导航到此 URL(意外地),然后似乎没有足够的上下文来知道将用户发送到哪里或采取什么操作.尽管这可能意味着它此时会落入错误路线。总的来说,不是一次很棒的体验。

        另一个问题是,如果您简化路线,路线之间的导航会变得更加简单,并且在不使用 $router.replace 的情况下表现得如预期/期望的那样。

        我知道这并不能解决您提出的问题。但我建议重新考虑这个加载路线。

        应用

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

        壳牌

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

        主页

        <section>
            <form>...</form>
        </section>
        
        {
            methods: {
                onSubmit() {
                    // ...
        
                    this.$router.push('/results')
                }
            }
        }
        

        结果页面

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

        结果组件

        基本上与您已经拥有的结果组件完全相同,但如果loading 为真,或者results 为空,无论您喜欢什么,您都可以创建一个假数据集来迭代并创建骨架版本,如果您'我愿意。否则,您可以保持原样。

        【讨论】:

        • 我的加载画面占据了整个屏幕,所以我想不出没有自己的路线的方式来显示它。有关现场演示,请参阅 naminator.io。接受任何其他想法。
        • 早上我会好好看看,但我最初的想法是你没有理由不能使用固定位置来接管屏幕。
        • 看看你的设计,似乎有几个选择:你可以渲染加载器来代替结果内容,然后在获取数据后切换;或者您可以覆盖结果骨架的加载器,然后更新和填充结果并删除加载器。考虑到加载器不覆盖标题(站点外壳),您不需要固定位置。
        • @ClickUpvote 告诉我您对这种方法的感受。
        • @ArrayKnight,问题是路由已经实现并且它按预期工作,这种加载方法可能不适合这里,我也经历过同样的情况。加载路径也可以有一个表单发布请求,如支付网关或一些表单发布操作。在这种情况下,我们不能包括删除路线。但是仍然在 vue 路由器级别,我们可以在进入守卫之前进行,为什么我建议这种方法是,加载组件的 vue 实例尚未在此钩子处创建,其优点是根据先前的路由决定是否进入此路由
        【解决方案5】:

        我猜router.replace 是要走的路——但仍有一些思路(未经测试):


        基本上在$router 更改时,它会渲染加载组件,直到它发出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 {}
        

        【讨论】:

        • 这就是我所说的那种实现,尽管我倾向于将我的加载组件直接实现到我的页面组件中,而不是创建一个路由监视包装器。原因是:我喜欢使用骨架方法,其中内容组件加载到半加载状态,以向用户提供预期的视觉反馈,然后覆盖加载组件(如果它需要阻止所有操作)或内联它,如果用户可以在数据加载时使用 UI。这完全是关于用户对速度的感知,并让他们尽快完成他们想做的事情。
        • @ArrayKnight 抽象思维可以简单地将路由器视图组件与其对应的路由组件交换并获得某种加载器包装器 - 但我同意将加载器放入路由感觉像是糟糕的设计
        【解决方案6】:

        有一个完美的方法来处理这种情况

        您可以使用in-component guard来控制颗粒级别的路由

        在您的代码中进行以下更改

        在主屏幕组件中

        在组件选项中添加这个 beofreRouteLeave 保护,在离开“结果屏幕”之前,您将路线设置为只走 通过加载屏幕

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

        在加载屏幕组件中

        如果路线从结果返回到加载然后,它不应该降落 这里直接跳转到主界面

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

        在加载屏幕中, beforeRouteEnter 守卫无权访问 这是因为在导航确认之前调用了守卫, 因此新的输入组件甚至还没有创建。因此,利用这一点,您不会在从结果屏幕路由时触发无限调用

        在结果屏幕组件中

        如果你使用 go back 那么它不应该直接加载 跳转到主屏幕

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

        我刚刚创建了小型 vue 应用程序来重现相同的问题。根据您的问题,它在我的本地工作。希望它也能解决您的问题

        【讨论】:

        • 此解决方案很脆弱(引入了维护技术债务),并且需要为您可能需要此功能的每条可能路线输入条目。
        • 完全同意,我一点也不喜欢这个解决方案
        【解决方案7】:

        这可以通过路由器的beforeEach钩子来完成。

        您需要做的是在加载数据时(在重定向到results 组件之前)必须在loading 组件中全局或localStorage 中保存一个变量:

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

        然后你应该在 beforeEach 钩子中检查这个变量并跳到正确的组件:

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

        另外,请记住在单击查询按钮时将 main 组件中的值重置为 false(在路由到 loading 组件之前):

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

        【讨论】:

        • 此解决方案很脆弱(引入了维护技术债务),并且需要为您可能需要此功能的每条可能路线输入条目。
        猜你喜欢
        • 2020-10-16
        • 2017-07-30
        • 2022-07-18
        • 1970-01-01
        • 2015-02-14
        相关资源
        最近更新 更多