上一篇sprite框架的文章介绍了如何通过require.js来实现javascirpt的模块化开发。

本篇文章主要介绍如何通过require.js的模块化能力来实现VueRouter对象的动态生成,这也是实现sprite框架的最核心的思想。


VueRouter是什么?

之前的文章里有提到过VueRouter是一个基于vue的前端路由框架,先来简单介绍下VueRouter。

VueRouter的主要作用是通过一些配置,将一个个前端url路径(即hash路由)映射到一个个vue对象上去,并且监听前端的路由变化,找到当前路径对应的vue对象来进行页面渲染以及业务逻辑控制。

要想构造一个VueRouter对象,首先要构造一个路由对象数组routes,routes的大致组成如下:

var routes = [
    {
        path:'/xxx1', //路由路径:访问该页面的路径
        name:'xxx1',  //路由名称:可以根据name直接跳转指定页面
        component:vueObj1  //该页面对应的vue对象
    },  {
        path:'/xxx2',
        name:'xxx2',
        component:vueObj2,
        children: [  //如果当前页面存在子页面,那么需要注册子页面
            {
                path:'/child', //子页面的路由路径 = 父页面路径+子页面路径 =/xxx2/child
                name:'child',  //可以直接通过child名称跳转子页面,但实际访问的页面路径是/xxx2/child 而不是/child
                component:vueObj3,  //子页面对应的vue对象
                children:[...]  //子页面也可以用自己的子页面
            },
            ...
        ]
    }
    ...
];

可以看出来routes数组其实就是由一个个配置了路径和vue对象映射关系的路由对象组成,这些配置好的对象构成了单页应用的骨架,然后统一由VueRouter来管控。

有了routes就可以初始化VueRouter对象了,但是光初始化VueRouter还不够,还需要其他的代码来配合。

下面我们来看一个VueRouter的使用实例:(在本地创建一个html文件并且将下面的代码拷贝进去,即可查看效果)

VueRouterTest.html

<!DOCTYPE html>
<html>
<head>
  <title>VueRouterTest</title>
  <script src="https://unpkg.com/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
  <div id="app">
    <!-- VueRouter会根据当前路径找到对应的vue对象,将其内容渲染到router-view标签中 -->
    <!-- 当路径发生变化时,router-view标签中的内容也将跟着变化 -->
    <router-view></router-view>
  </div>
</body>
<script>
//先定义两个vue对象,分别代表首页Index和内容页Content
var Index = {
  template: `<div>
            <span>这是首页</span>
            <button @click="goContent">点击前往内容页</button>
            </div>`,
  data:function(){
     return {
        helloText:'欢迎来到首页'
    }
  },
  created:function(){
    this.sayHello();
  },
  methods:{
    sayHello:function(){
     console.log(this.helloText);
   },
   goContent:function(){
    //编程式路由跳转,通过name跳到内容页
    this.$router.push({
      name:'content'
    });
   }
  }
};

var Content = {
  template: `<div>
            <span>这是內容页</span>
            <div>{{content}}</div>
            </div>`,
  data:function(){
    return {
        content:''
    }
  },
  created:function(){
    this.setContent();
  },
  methods:{
    setContent:function(){
      this.content = "一些内容一些内容"
    }
  }
};

//然后定义路由数组routes
var routes = [
  {
    path:'/',  //配置根路径,效果是将Index配置为默认页面,当hash路由为空时,默认访问该页面
    name:'index',
    component:Index
  },
  {
    path:'/index',
    name:'index',
    component:Index
  },
  {
    path:'/content',
    name:'content',
    component:Content
  }
];

//使用routes数组初始化VueRouter对象
var router = new VueRouter({
  routes:routes 
});

//使用router对象初始化主体vue对象
var app = new Vue({
  el:'#app',
  router:router
});

</script>
</html>

当我们打开上面代码对应html文件,将会看到如下页面:


由于刚进入页面,hash路由值为空,所以会访问根路径对应的页面也就是Index页。

也可以直接通过”/index”路径访问Index页:

在index页点击按钮将会跳转Content页:

这就是VueRouter的使用方法了,其实上面这个例子就是一个单页应用了,所有的页面都会在id为“app”的div中渲染,由VueRouter来监听路由的变化,由Vue负责页面的渲染和业务逻辑控制。

require.js + VueRouter实现路由对象动态生成

上面的例子存在一个问题,那就是routes数组的生成过程是静态的,而且存在同一个js文件中,如果开发的网站有很多个页面,那么这个js文件将会变得非常臃肿,开发者之间也难以协同工作。

所以就需要使用上一篇文章提到js模块化开发了,也就是将routes数组内的路由对象中最重要的组成部分,即component属性对应的vue对象进行模块化拆分。

比如上文中的例子,就可以将Index和Content两个vue对象拆分到index.js和content.js中去,然后使用require.js加载这两个js文件获取其中定义的vue对象,再组织成routes数组。

sprite就是利用了这种方式来实现应用的页面注册机制,开发者先分别进行页面vue对象的开发,然后在pageRegister.js中配置好vue对象所在js文件的地址以及路由路径、路由名称、子路由等信息,如下所示:

pageRegister.js

window.PAGE_ARR = [{
        id: "zero",
        vueJsPath: "modules/page0/page0",
        vueRoute: "/zero",
        vueRouteName: "zero",
        isIndex: true
    },
    {
        id: "one",
        vueJsPath: "modules/page1/page1",
        vueRoute: "/one",
        vueRouteName: "one",
        childrenIds: ['page1_1', 'page1_2']
    },
    {
        id: "page1_1",
        vueJsPath: "modules/page1/page1_1/page11",
        vueRoute: "/page1_1",
        vueRouteName: "page1_1",
        childrenIds: ['page1_1_1']
    },
    {
        id: "page1_2",
        vueJsPath: "modules/page1/page1_2/page12",
        vueRoute: "/page1_2",
        vueRouteName: "page1_2"
    },
    {
        id: "page1_1_1",
        vueJsPath: "modules/page1/page1_1/page1_1_1/page111",
        vueRoute: "/page1_1_1",
        vueRouteName: "page1_1_1"
    },
    {
        id: "two",
        vueJsPath: "modules/page2/page2",
        vueRoute: "/two",
        vueRouteName: "two"
    }
];

框架会在入口文件main.js中根据pageRegister.js配置的信息来加载所有vue对象,并且利用递归解析的方式将所有vue对象组织起来生成VueRouter所需的routes数组,之所以要递归解析是因为上文提到的页面可以拥有子页面,而子页面也可以用于自己的子页面,代码如下:

main.js

require(REQUIRE_MODULES.all, function(Vue, VueRouter, mintUI, util, routeParse, $, axios) {
    //将axios输出到全局作用域
    window.axios = axios;
    //使用vue路由组件
    Vue.use(VueRouter);
    //使用饿了么移动端组件mint-ui
    Vue.use(mintUI);

    //根据pageRegister.js中的配置生成vue路由数组
    var routes = routeParse.getVueRoute(arguments);
    //生成VueRouter对象
    var router = new VueRouter({
        routes: routes
    });

    //挂载主vue对象
    app = new Vue({
        el: '#app',
        router
    });

});

上面代码中的REQUIRE_MODULES.all不仅仅包括默认的如vue,mint-ui,jquery组件库,还包括了所有开发者自己开发的页面vue对象的文件路径,因此在require的回调函数中可以通过arguments参数对象拿到请求得到vue对象,从而进行解析。

其实关于pageRegester.js中定义的配置信息PAGE_ARR是可以更进一步的动态化的,比如配置到xml文件中或者数据库中,然后通过一次网络请求,在后端进行查询和组装后返回前端,如此一来就可以利用这种方式来实现页面鉴权(后面会再写一篇文章介绍我们部门是如何实现页面鉴权的

解析routes数组的代码如下所示:

routeParse.js

define(function() {
    return {
        getVueRoute: function(_arguments) {
            var self = this;
            var routes = [];
            //获取arguments参数数组,从第一个非组件的位置开始获取,之后的所有参数都认为是vue页面
            var pages = [];
            for (var i = REQUIRE_MODULES.default.length; i < _arguments.length; ++i) {
                var pageInit = _arguments[i];
                pages.push(pageInit());
            }
            //子节点列表
            var childrenIdList = [];
            for (var i = 0; i < PAGE_ARR.length; ++i) {
                PAGE_ARR[i].index = i;
                PAGE_ARR[i].component = pages[i];
                if (PAGE_ARR[i].childrenIds && PAGE_ARR[i].childrenIds instanceof Array) {
                    childrenIdList = childrenIdList.concat(PAGE_ARR[i].childrenIds);
                }
            }
            //构建子节点映射,通id可以获取到这个子节点,并且在原数组中标记子节点的isChild为true
            var childrenObjs = {};
            for (var i = 0; i < childrenIdList.length; ++i) {
                for (var j = 0; j < PAGE_ARR.length; ++j) {
                    if (PAGE_ARR[j].id === childrenIdList[i]) {
                        PAGE_ARR[j].isChild = true;
                        var key = PAGE_ARR[j].id;
                        childrenObjs[key] = PAGE_ARR[j];
                    }
                }
            }

            //标志是否存在首页
            var indexExists = false;
            //把PAGE_ARR解析为vue的路由数组
            for (var i = 0; i < PAGE_ARR.length; ++i) {
                var page = PAGE_ARR[i];
                if (!page.isChild) {
                    //页面对应vue组件
                    var pageComponent = page.component;
                    //路由跳转时用到的名称
                    var pageName = page.vueRouteName;
                    //路由路径
                    var pagePath = page.vueRoute;

                    //判断是否是首页
                    if (!indexExists && page.isIndex) {
                        routes.push({
                            path: '/',
                            component: pageComponent
                        });
                        //每个应用只有一个首页
                        indexExists = true;
                    }
                    var routeObj = {};
                    routeObj.path = pagePath;
                    routeObj.component = pageComponent;
                    routeObj.name = pageName;
                    //递归添加子路由
                    self.addChildrenRoute(page, routeObj, childrenObjs);
                    //将当前页面增加到vue的路由对象中
                    routes.push(routeObj);
                }
            }

            return routes;
        },
        /**
         * 递归解析页面子路由
         * page:当前页面
         * routeObj:当前页面对应的路由对象
         * childrenObjsMap:子页面对象映射表
         */
        addChildrenRoute: function(page, routeObj, childrenObjsMap) {
            var self = this;
            //如果当前页面存在子页面,则将子页面加入当前页面的子路由
            if (page.childrenIds && page.childrenIds instanceof Array) {
                routeObj.children = [];
                page.childrenIds.map(function(childId, index) {
                    //,使用子页面的id通过子页面对象映射表获取子页面对象
                    var child = childrenObjsMap[childId];
                    var childrouteObj = {};
                    childrouteObj.component = child.component;
                    childrouteObj.name = child.vueRouteName;
                    childrouteObj.path = child.vueRoute.substr(child.vueRoute.indexOf('/') + 1);
                    //递归添加子页面的嵌套子页面
                    self.addChildrenRoute(child, childrouteObj, childrenObjsMap);
                    routeObj.children.push(childrouteObj);
                });
            }
        }
    }
})

代码的含义大部分都在注释中解释了,主要的思想也在上文中介绍了,所以这里就不在赘述了,如果觉得看代码显得不直观,那么建议将框架demo源码下载到本地进行debug来查看,这样的话会更直观~


关于在sprite框架中require.js和VueRouter的配合使用就介绍到这里了,其实sprite框架的主体思路和实现到这就介绍完毕了。

不过在这里要说明的是,目前贴到文中的代码仅仅是sprite框架的最简版代码。

在后来的使用过程中(我们部门在最近的这三个多月的时间里,利用sprite框架开发出了20多个移动应用),sprite框架也不断地增加新的内容,比如角色选择,页面授权,按钮授权,微信SDK的初始化,以及其他外部js库的引入以及我自己对框架代码的优化调整等等,使得现在的框架代码越发丰富起来。

我之后会从框架新增的内容再挑一些有意义的内容来介绍,所以sprite框架系列的文章到这里还远远没有结束噢~

发表评论

电子邮件地址不会被公开。 必填项已用*标注