上一篇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框架系列的文章到这里还远远没有结束噢~