【问题标题】:Fully dynamic vue-router完全动态的 vue-router
【发布时间】:2019-06-05 14:39:09
【问题描述】:

我们正在基于 Vue 和 Nuxt 构建一个庞大的网站,其中包含超过 25 种不同的页面类型,这些页面类型无法与 Vue Router 开箱即用的标准 /:id 或 /overview/:slug 逻辑匹配。

由于无法进行 slug 匹配,我们正在考虑以下解决方案:

  1. 用户访问页面“/this-is-a-topic-page”
  2. 服务器调用返回pageTypetopicPage的API
  3. topicPage 涉及到 nuxt 页面 WpTopicPage
  4. 我们将 WpTopicPage 设置为 Vue 路由器通配符实例中的组件

这在代码中如下所示:

export function createRouter() {
  return new Router({
    mode: 'history',
    routes: [
      // 1. User visits page "/this-is-a-topic-page"
      {
        name: 'wildcard',
        path: '*',
        component: *, // this should be dynamic
        beforeEnter: (to, from, next) => {
          // 2. Server calls API that returns the pageType `topicPage`
          this.$axios.get(`/call-to-the-api?slug=${to.params.slug}`)
            .then((res) => {
              // 3. `topicPage` relates to the nuxt page `WpTopicPage`
              if(res.data.pageType === 'topicPage') {
                // 4. Set `WpTopicPage` as our Page component
                return WpTopicPage;
              }
            })
        },
      },
    ],
  });
}

以上显然行不通。有没有办法在 beforeEnter 函数中动态设置路由内的component

【问题讨论】:

  • 也许Async Components 可以提供帮助
  • 或者你可以使用Dynamic Components
  • @ljubadr。这是路由器实例,而不是组件。动态组件可以工作(我们目前有),但缺点是您渲染一个页面(它具有 Nuxt 的优势,如异步 Fetch 或 asyncData),它会切换一个动态组件。这意味着我们无法获取特定于页面的存储,因为动态组件不是 Nuxt Page 实例,而是实际组件。

标签: vue.js vue-router nuxt.js


【解决方案1】:

我们当前的解决方案不违反守卫中“只读”tofrom 参数的要求,但它需要访问路由内部来安装代理,因此不太标准推荐的方法“替换路由并重定向到被替换的路由”。但是,如果您不想每次都重写路由树,它会很有用。

首先我们安装一个守卫来将新路由保存在一个全局变量中。对于路由器的上下文足够全局。

router.beforeEach( (to,from) => {nuevaRuta = to })

我们保存了to,但它仍然是“只读”的。看看如何:我们使用代理定义组件和道具:

    var nuevaRuta = 'None'
    const proxyComponentes = {
        get: function (target, prop, receiver) {
          return FriendlyIframe;
        },
        ownKeys: (oTarget, sKey) => { 
          if (nuevaRuta == 'None' || typeof nuevaRuta.params.panes == 'undefined')
             return Reflect.ownKeys(oTarget,sKey)
          else {
             return nuevaRuta.params.panes
          } 
        },
        getOwnPropertyDescriptor(target, prop) { // called for every property
          return {
            enumerable: true , configurable: true  /* ...other flags, probable "value:..." */
          };
        }
      };
   const proxyPropiedades = {
       get: function (target, prop, receiver) {
         console.log(prop)
         if (prop.startsWith('user_')){
           var id = prop.replace('user_','')
           return {src: `/u/${id}?noheader=true`}
         }else{
           return {src: `/p/${window.PRID}/${prop}?noheader=true`}
         }
        }};
    var myComponents = {};  
    var myProps= {}; 

我们在构建路由匹配时安装它们。更准确地说,对于版本 4 的路由器,可以在路由定义期间安装组件代理:

routes:[
    {
      name: "generica",
      path: '/:panes+',
      components: new Proxy ( myComponents, proxyComponentes),
      props: false

但是 props 是通过规范化“缓存”的,所以我们需要在之后安装它。在我看来,这是最令人不安的细节

router.getRoutes().find( (e) => e.name=="generica").props = new Proxy (myProps, proxyPropiedades)

当然,如果路由器在处理组件时实现缓存和进一步优化,该方法可能会停止工作。如果您想要一个长期安全的解决方案,请不断更新和重定向。

【讨论】:

    【解决方案2】:

    前段时间我一直在为类似的任务而苦苦挣扎。我还需要一个完全动态的路由器,但我的应用程序初始化顺序有点不同。

    1. 在 Vue 实例化时 (@main.js new Vue({...})),我没有路由,也没有相关组件。
    2. 同时,我从服务器异步请求初始数据(并显示加载动画)
    3. 它一到,我就在映射我的路由器

    很酷的是,可以在任何时间点映射和重新映射路由器。
    我认为即使您的初始化序列,您也可以使用我的实现。

    这是我的router.js

    import Vue from 'vue';
    import Router from 'vue-router';
    
    Vue.use(Router);
    
    const createRouter = () =>
      new Router({
        mode: 'history',
        linkActiveClass: 'active',
        base: __dirname,
        routes: []
      });
    
    const router = createRouter();
    
    export function resetRouter() {
      const newRouter = createRouter();
      router.matcher = newRouter.matcher;
    }
    
    export default router;
    

    注意有resetRouter 函数。我认为它的作用是不言自明的。

    一旦您的应用知道需要映射/使用哪种类型的路由和组件,您就可以创建路由集合并映射它们。像这样:

    import { default as router, resetRouter } from '@/router';
    
    // ...
    
    let routes = [];
    
    // some magic to fill the routes array
    // I.e. items coming from API
    items.forEach(item => {
      if (item.view) {
        const component = () => import(`@/views/${item.view}.vue`);
    
        const route = {
          name: null, // prevent duplicate named routes warning
          path: item.path,
          component,
          meta: {
            title: item.title
          }
        };
        routes.push(route);
      }
    });
    
    resetRouter();
    router.addRoutes(routes);
    

    【讨论】:

      【解决方案3】:

      @AndrewShmig 和@fjemi 的两个回答都是极好的想法,唯一的问题是他们不同意 Vue Router 的预期设计。似乎 Vue 开发人员希望“to”是静态的,除了元字段,并且组件中的函数应该是一个承诺,在第一次使用路由后将被缓存。

      我认为同意文档的唯一方法是使用 BeforeEnter 挂钩将新组件推送到“/*”路由中,标记元字段,然后返回重定向。这将导致重新进入钩子,这次使用正确的组件。用每个条目替换路由有点令人不安,但希望它不会导致内存泄漏。由于我们正在替换一个预先存在的路由,我们可以期望它在路由器的一侧进行优化 - 但我还没有检查过 -。

      【讨论】:

        【解决方案4】:

        这里有一个解决方案。

        • Home.vueNotFound.vue 的路由是手动设置的
        • views 目录中的其他视图 (*.vue) 动态创建路由
        • 路由的路径是.vue文件的小写,Home.vue除外,它设置为/。例如)FileName.vue 的路径是 /filename

        项目结构

        app
        |__router
           |__index.js
        |__views
           |__Home.vue
           |__NotFound.vue
           |__*.vue
        

        index.js

        import Vue from "vue";
        import VueRouter from "vue-router";
        
        // list of files in the `views` directory
        const views = require.context(
          `../views`,
          true,
          /^.*\.vue$/
        )
        
        Vue.use(VueRouter);
        
        // routes
        const routes = [{
          path: "/",
          name: "Home",
          component: import("../views/Home.vue")
        }]
        
        // dynamically add all routes in views directory
        for (var i = 0; i < views.keys().length; i++) {
          // skip home and notfound since it is defined above
          if (
            views.keys()[i].slice(2, -4) !== 'Home' &&
            views.keys()[i].slice(2, -4) !== 'NotFound' 
            ) {
            // file path for the route
            let filePath = `views/${views.keys()[i].slice(2, -4)}.vue`
            // add routes
            routes.push({
              path: views.keys()[i].slice(1, -4).toLowerCase(),
              name: views.keys()[i].slice(2, -4),
              component: () =>
                import("../" + filePath)
            })
          }
        }
        
        // directs any undefined route to not found 
        routes.push({
          path: "*",
          name: "NotFound",
          component: () => import("../views/NotFound.vue")
        })
        
        const router = new VueRouter({
          routes: routes
        });
        
        export default router
        

        【讨论】:

          【解决方案5】:

          这是可以做到的。我创建了一个codepen 供您测试:

          这里是:

          Vue.use(VueRouter);
          
          let A = {
            mounted() {
              console.log('Mouted component A');
            },
          };
          let B = {
            mounted() {
              console.log('Mouted component B');
            },
          };
          let C = {
            mounted() {
              console.log('Mouted component C');
            },
          };
          
          const router = new VueRouter({
            mode: "hash",
            routes: [
              {
                path: '*',
                beforeEnter(to, from, next) {
                  let components = {
                    default: [A, B, C][Math.floor(Math.random() * 100) % 3],
                  };
                  to.matched[0].components = components;
                  
                  next();
                }
              },
            ]
          });
          
          app = new Vue({
            router,
            el: '#app',
            components: { A, B, C }
          });
          <script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.2/vue-router.min.js"></script>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
          <div id="app">
            <router-link :to="'/' + Math.random()">anything</router-link>
            <router-view></router-view>
          </div>

          这是输出:

          正如您在控制台日志中看到的那样 - 每次发生变化时,我们都会加载并安装随机组件。

          【讨论】:

          • 这是个好主意,但 to.matched 在钩子期间应该被认为是静态的、恒定的。
          猜你喜欢
          • 2019-06-30
          • 2018-06-12
          • 2018-04-01
          • 2020-04-22
          • 2020-07-19
          • 2020-12-09
          • 2020-03-15
          • 2017-03-15
          • 1970-01-01
          相关资源
          最近更新 更多