zoukankan      html  css  js  c++  java
  • Vue自动化路由(基于Vue-Router)开篇

    vue自动化路由

    好久不见~ 若羽又开篇Vue的内容了。
    年初的时候发布了第一版的ea-router自动化路由库,欢迎大家安装使用。[Github地址] [npm地址]

    经历一年的使用。还是发现了不少问题和不足的地方,因此在前段时间抽空整理了所有需求并做了个规划。并发布了一个版本。下面来看看其中的原理和实现吧。

    前言

    因为之前都是写后端逻辑,因此接触前端后始终不太习惯js的原生语法。更偏向于es6的class写法,并且从ECMAScript后续的标准来看,官方也是比较推荐class的写法来更好的组织代码,并使其具有更强的表义性。哈哈,当然因为更熟悉后者,所以更偏袒一点。

    功能需求

    功能规划图

    功能主要分为两部分:

    • 路由自动化
    • 服务于库的装饰器

    路由自动化中,除了原有的自动生成外,还增加了另外两个在业务中会经常使用到的功能:

    1. 设置缺省的Layout
    2. 设置缺省的404页面

    目录中的子目录关系,用路由中嵌套路由来进行表达,因此需要一个入口进行渲染,这就是Layout存在的一个意义,另外一层则是作为某个模块的通用布局存在。

    装饰器主要用于补充路由相关特性,比如vue-router中的各种特性(命名路由别名params等等),无缝的接入业务中。就像vue-property-decorators库一样。

    原理

    原理图

    为了达成自动化路由的目的,本质就是要将路由对象按照某种特定的规则进行生成即可。那么参考后端MVC中的路由以及其他前端路由框架,将需要路由的页面按照目录的层次结构进行组织,然后对目录进行解析是比较通用并容易实现的。

    1. 扫描目录文件
    2. 还原目录结构
    3. 转换为目录对象
    4. 加载适配器(默认为vue-router的适配器)
    5. 适配器将目录对象转换为routes
    6. 使用routes

    目录对象

    将实际的目录结构映射成对象,下面看一个例子:

    目录结构如下:

    views
    |-- About.vue
    |-- Home.vue
    |-- Layout.vue
    |-- user
        |-- Add.vue
    

    router/index.js代码如下:

    // /src/router/index.js
    
    import Vue from "vue";
    import VueRouter from "vue-router";
    import AutoRouteGenerator from "ea-router";
    import defaultLayout from "../components/defaultLayout";
    import notFoundPage from "../components/notFound";
    
    Vue.use(VueRouter);
    let generator = new AutoRouteGenerator(
      require.context("../views", true, /.vue$/)
    );
    
    generator.setDefaultLayout(defaultLayout);
    generator.setNotFoundPage(notFoundPage);
    
    const routes = generator.generate();
    const router = new VueRouter({
      routes
    });
    
    export default router;
    

    对应vue-router,自动生成的路由对象会是如下形式(里面的对象是自动生成的,导出语句不是喔,只是为了演示):

    const routes = [
      {
        path: "/",
        component: () => import("src/views/layout.vue"),
    
        children: [
          {
            path: "home/:id/:name",
            component: () => import("src/views/home.vue"),
            props: true
          },
    
          {
            path: "about",
            component: () => import("src/views/about.vue")
          },
    
          {
            path: "user",
            component: () => import("src/components/defaultLayout.vue"),
    
            children: [
              {
                path: "add",
                component: () => import("src/views/user/add.vue")
              }
            ]
          }
        ]
      },
    
      {
        path: "*",
        component: () => import("src/components/notFound.vue")
      }
    ];
    export default routes;
    

    因为使用的是webpackrequire.context函数,但是它有一个缺陷就是扫描出来的并不是目录原来的层次结构。而是一维的结构,因此我们首先要还原原来的层次结构,并在此基础上封装、解析一些必要的信息。

    适配器

    适配者模式在这个场景下非常合适,输入是解析后的目录对象,而输出则是变化的。有可能是:

    • vue-router的路由对象routes
    • vue-router-next的路由对象routes
    • 其他路由框架的路由对象
      想要适配其他框架, 则只需要实现对应的适配器并加载即可。

    使用

    目前有3个api以及5个装饰器

    api:

    decorators:

    generate api

    构造函数中传入通过require.context指定目录及过滤规则, 如下实例是指定views目录下所有.vue文件。

    路由生成的api, 调用此方法将生成一个对应 路由适配器 生成的路由对象,目前默认内置的时基于vue 2.xvue-router

    // src/router/index.js
    
    import Vue from 'vue'
    import Router from 'vue-router'
    import RouteGenerator from "ea-router";
    
    Vue.use(Router)
    let generator = new RouteGenerator(require.context('./views', true, /.vue$/))
    
    export default new Router({
      routes: generator.generate()
    })
    

    那么在 main.js 中,我们不用改动原有的代码即可直接使用:

    // src/main.js
    
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    
    Vue.config.productionTip = false
    
    new Vue({
      router,
      render: h => h(App)
    }).$mount('#app')
    

    setDefaultLayout api

    指定默认的Layout.vue,因为在大多数情况下,Layout.vue的内容可能都是下面这样:

    <template>
        <router-view></router-view>
    </template>
    

    这种情况下,Layout.vue的目的仅仅是作为子路由的入口。那么我们可以直接利用setDefaultLayout来设置默认的Layout

    规则如下:

    • 当当前目录中没有Layout.vue时,会尝试使用设置的默认Layout
    • 当没有Layout.vue并且没有设置默认Layout时,将会抛出异常。

    实例:

    // src/router.js
    
    import Vue from 'vue'
    import Router from 'vue-router'
    import RouteGenerator from "ea-router";
    import DefaultLayout from './components/defaultLayout.vue';
    
    Vue.use(Router)
    let generator = new RouteGenerator(require.context('./views', true, /.vue$/))
    generator.setDefaultLayout(DefaultLayout);
    
    export default new Router({
      routes: generator.generate()
    })
    
    <!-- /src/components/defaultLayout.vue -->
    <template>
        <router-view></router-view>
    </template>
    

    setNotFoundPage api

    设置路由匹配失败时显示的页面。

    实例:

    // src/router.js
    
    import Vue from 'vue'
    import Router from 'vue-router'
    import RouteGenerator from "ea-router";
    import NotFoundPage from './components/notFound.vue';
    
    Vue.use(Router)
    let generator = new RouteGenerator(require.context('./views', true, /.vue$/))
    generator.setNotFoundPage(NotFoundPage);
    
    export default new Router({
      routes: generator.generate()
    })
    
    <!-- /src/components/notFound.vue -->
    <template>
        <div>
            嘿,页面走丢啦!    
        </div>
    </template>
    

    @RouteName(name: string) decorator

    设置路由名称,在vue-router中对应了命名路由

    import { Vue, Component } from 'vue-property-decorator';
    import { RouteName } from 'ea-router';
    
    @RouteName('YourComponentRouteName')
    @Component
    export default class YourComponent extends Vue {
    }
    

    等价于

    const router = new VueRouter({
      routes: [
        { 
            path: 'path/to/component/on/directory', 
            name: 'YourComponentRouteName',
            component: YourComponent,
        }
      ]
    })
    

    Note that: path的生成规则是相对路径噢(根目录是构造函数中传入的目录,示例中也就是src/views

    @Alias(alias: string) decorator

    设置路由别名,对应vue-router中的别名

    import { Vue, Component } from 'vue-property-decorator';
    import { Alias } from 'ea-router';
    
    @Alias('YourComponentAlias')
    @Component
    export default class YourComponent extends Vue {
    }
    

    等价于

    const router = new VueRouter({
      routes: [
        { 
            path: 'path/to/component/on/directory', 
            alias: 'YourComponentAlias',
            component: YourComponent,
        }
      ]
    })
    

    @Context(params: string[]) decorator

    设置路由上下文,对应了vue-router中的$routes.params

    会根据传入的顺序生成path

    import { Vue, Component } from 'vue-property-decorator';
    import { Context } from 'ea-router';
    
    @Context('id', 'type')
    @Component
    export default class YourComponent extends Vue {
    }
    

    等价于

    const router = new VueRouter({
      routes: [
        { 
            path: 'path/to/component/on/directory/:id/:type',
            component: YourComponent,
        }
      ]
    })
    

    Note that: 如果同时使用 @Alias@Context, 上下文的参数会自动添加在alias后面, 就像下面的例子:

    import { Vue, Component } from 'vue-property-decorator';
    import { Context, Alias } from 'ea-router';
    
    @Alias('YourComponentAlias')
    @Context('id', 'type')
    @Component
    export default class YourComponent extends Vue {
    }
    

    等价于

    const router = new VueRouter({
      routes: [
        { 
            path: 'path/to/component/on/directory/:id/:type',
            alias:'YourComponentAlias/:id/:type',
            component: YourComponent,
        }
      ]
    })
    

    @EnableProps() decorator

    开启路由参数的Boolean模式, 对应了vue-router中的路由传参-布尔模式

    import { Vue, Component } from 'vue-property-decorator';
    import { EnableProps } from 'ea-router';
    
    @EnableProps()
    @Component
    export default class YourComponent extends Vue {
    }
    

    等价于

    const router = new VueRouter({
      routes: [
        { 
            path: 'path/to/component/on/directory',
            props: true,
            component: YourComponent,
        }
      ]
    })
    

    Note that: 一般搭配 @Context 使用。

    @Meta(meta: object) decorator

    设置路由元信息,对应vue-router中的路由元信息

    import { Vue, Component } from 'vue-property-decorator';
    import { Meta } from 'ea-router';
    
    @Meta({
        title: 'Component Title',
        requireAuthorize: true
    })
    @Component
    export default class YourComponent extends Vue {
    }
    

    等价于

    const router = new VueRouter({
      routes: [
        { 
            path: 'path/to/component/on/directory',
            component: YourComponent,
            meta: {
                      title: 'Component Title',
                      requireAuthorize: true
                  },
        }
      ]
    })
    

    建议

    在开发过程中,使用Class形式的写法是最为推荐的,表义性和组织性会更强一些。配合vue-property-decorators 食用更佳喔。好了,接下来说正经的:

    1. 路由跳转,建议使用命名路由的跳转方式。去查看相关文档
    2. 给路由命名,并统一定义路由的名称,便于管理(如,都定义在/src/domain/views.js中)。
    3. 路由上下文使用Props进行传参。

    计划

    • 实现 vue-router-next 的适配器
    • 实现路由文件的自动生成(基于模板语法)
    • 添加可设置所有选项配置的装饰器
    • 开放加载自定义适配器
    • typescript支持
    • 回补单元测试

    总结

    做这个库之前,也查找了很多相关资料。并且翻了不少类似库的源码进行学习,发现比较常见的做法:

    1. 动态加载,即请求时去import 实现动态加载。但这个只是做了自动寻找路由,对于路由的组织还是没有比较好的解决。
    2. webpack动态解析路径,通过正则表达式或者vue单文件组件解析器对文件进行解析,提取内容。这种方式非常接近本文中的方式,但是缺点也比较明显:不支持变量,如果全部硬编码到文件里,管理也是一个问题。

    最后结合大家的经验,实现了这个库。下一步也会考虑开始实现生成路由文件,补充这一块的空白。

    关于自动化路由这部分,将会从分析实现使用以及后续开发都会记录下来,并且会开源用了此库的一些个人项目,形成系列文章。这篇就当是起个头,如有不足,欢迎各位指正。

    ~另外欢迎大家使用并提出宝贵的意见哟

  • 相关阅读:
    Hql语句注意事项总结
    数据库主键设计之思考
    UTF8的中文问题
    DirectShow SDK笔记【关于DirectShow(4)】
    关于kindeditor上传图片出现"服务器发生故障"的解决办法
    php 分隔字符串为数组
    yum 一次性安装 apache mysql php
    linux下安装gd库
    三种实现PHP伪静态页面的方法
    (转)Linux利器 strace
  • 原文地址:https://www.cnblogs.com/By-ruoyu/p/14252144.html
Copyright © 2011-2022 走看看