路由的封装
小程序提供了路由功能来实现页面跳转,但是在使用的过程中我们还是发现有些不方便的地方,通过封装,我们可以实现诸如路由管理、简化api等功能。
页面的跳转存在哪些问题呢?
与接口的调用一样面临url的管理问题;
传递参数的方式不太友好,只能拼装url;
参数类型单一,只支持string。
alias
第一个问题很好解决,我们做一个集中管理,比如新建一个[代码]router/routes.js[代码]文件来实现alias:
[代码]// routes.js
module.exports = {
// 主页
home: '/pages/index/index',
// 个人中心
uc: '/pages/user_center/index',
};
[代码]
然后使用的时候变成这样:
[代码]const routes = require('../../router/routes.js');
Page({
onReady() {
wx.navigateTo({
url: routes.uc,
});
},
});
[代码]
query
第二个问题,我们先来看个例子,假如我们跳转[代码]pages/user_center/index[代码]页面的同时还要传[代码]userId[代码]过去,正常情况下是这么来操作的:
[代码]const routes = require('../../router/routes.js');
Page({
onReady() {
const userId = '123456';
wx.navigateTo({
url: `${routes.uc}?userId=${userId}`,
});
},
});
[代码]
这样确实不好看,我能不能把参数部分单独拿出来,不用拼接到url上呢?
可以,我们试着实现一个[代码]navigateTo[代码]函数:
[代码]const routes = require('../../router/routes.js');
function navigateTo({ url, query }) {
const queryStr = Object.keys(query).map(k => `${k}=${query[k]}`).join('&');
wx.navigateTo({
url: `${url}?${queryStr}`,
});
}
Page({
onReady() {
const userId = '123456';
navigateTo({
url: routes.uc,
query: {
userId,
},
});
},
});
[代码]
嗯,这样貌似舒服一点。
参数保真
第三个问题的情况是,当我们传递的参数argument不是[代码]string[代码],而是[代码]number[代码]或者[代码]boolean[代码]时,也只能在下个页面得到一个[代码]string[代码]值:
[代码]// pages/index/index.js
Page({
onReady() {
navigateTo({
url: routes.uc,
query: {
isActive: true,
},
});
},
});
// pages/user_center/index.js
Page({
onLoad(options) {
console.log(options.isActive); // => "true"
console.log(typeof options.isActive); // => "string"
console.log(options.isActive === true); // => false
},
});
[代码]
上面这种情况想必很多人都遇到过,而且感到很抓狂,本来就想传递一个boolean,结果不管传什么都会变成string。
有什么办法可以让数据变成字符串之后,还能还原成原来的类型?
好熟悉,这不就是json吗?我们把要传的数据转成json字符串([代码]JSON.stringify[代码]),然后在下个页面把它转回json数据([代码]JSON.parse[代码])不就好了嘛!
我们试着修改原来的[代码]navigateTo[代码]:
[代码]const routes = require('../../router/routes.js');
function navigateTo({ url, data }) {
const dataStr = JSON.stringify(data);
wx.navigateTo({
url: `${url}?jsonStr=${dataStr}`,
});
}
Page({
onReady() {
navigateTo({
url: routes.uc,
data: {
isActive: true,
},
});
},
});
[代码]
这样我们在页面中接受json字符串并转换它:
[代码]// pages/user_center/index.js
Page({
onLoad(options) {
const json = JSON.parse(options.jsonStr);
console.log(json.isActive); // => true
console.log(typeof json.isActive); // => "boolean"
console.log(json.isActive === true); // => true
},
});
[代码]
这里其实隐藏了一个问题,那就是url的转义,假如json字符串中包含了类似[代码]?[代码]、[代码]&[代码]之类的符号,可能导致我们参数解析出错,所以我们要把json字符串encode一下:
[代码]function navigateTo({ url, data }) {
const dataStr = encodeURIComponent(JSON.stringify(data));
wx.navigateTo({
url: `${url}?encodedData=${dataStr}`,
});
}
// pages/user_center/index.js
Page({
onLoad(options) {
const json = JSON.parse(decodeURIComponent(options.encodedData));
console.log(json.isActive); // => true
console.log(typeof json.isActive); // => "boolean"
console.log(json.isActive === true); // => true
},
});
[代码]
这样使用起来不方便,我们封装一下,新建文件[代码]router/index.js[代码]:
[代码]const routes = require('./routes.js');
function navigateTo({ url, data }) {
const dataStr = encodeURIComponent(JSON.stringify(data));
wx.navigateTo({
url: `${url}?encodedData=${dataStr}`,
});
}
function extract(options) {
return JSON.parse(decodeURIComponent(options.encodedData));
}
module.exports = {
routes,
navigateTo,
extract,
};
[代码]
页面中我们这样来使用:
[代码]const router = require('../../router/index.js');
// page home
Page({
onLoad(options) {
router.navigateTo({
url: router.routes.uc,
data: {
isActive: true,
},
});
},
});
// page uc
Page({
onLoad(options) {
const json = router.extract(options);
console.log(json.isActive); // => true
console.log(typeof json.isActive); // => "boolean"
console.log(json.isActive === true); // => true
},
});
[代码]
route name
这样貌似还不错,但是[代码]router.navigateTo[代码]不太好记,[代码]router.routes.uc[代码]有点冗长,我们考虑把[代码]navigateTo[代码]换成简单的[代码]push[代码],至于路由,我们可以使用[代码]name[代码]的方式来替换原来[代码]url[代码]参数:
[代码]const routes = require('./routes.js');
function push({ name, data }) {
const dataStr = encodeURIComponent(JSON.stringify(data));
const url = routes[name];
wx.navigateTo({
url: `${url}?encodedData=${dataStr}`,
});
}
function extract(options) {
return JSON.parse(decodeURIComponent(options.encodedData));
}
module.exports = {
push,
extract,
};
[代码]
在页面中使用:
[代码]const router = require('../../router/index.js');
Page({
onLoad(options) {
router.push({
name: 'uc',
data: {
isActive: true,
},
});
},
});
[代码]
navigateTo or switchTab
页面跳转除了navigateTo之外还有switchTab,我们是不是可以把这个差异抹掉?答案是肯定的,如果我们在配置routes的时候就已经指定是普通页面还是tab页面,那么程序完全可以切换到对应的跳转方式。
我们修改一下[代码]router/routes.js[代码],假设home是一个tab页面:
[代码]module.exports = {
// 主页
home: {
type: 'tab',
path: '/pages/index/index',
},
uc: {
path: '/pages/a/index',
},
};
[代码]
然后修改[代码]router/index.js[代码]中[代码]push[代码]的实现:
[代码]function push({ name, data }) {
const dataStr = encodeURIComponent(JSON.stringify(data));
const route = routes[name];
if (route.type === 'tab') {
wx.switchTab({
url: `${route.path}`, // 注意tab页面是不支持传参的
});
return;
}
wx.navigateTo({
url: `${route.path}?encodedData=${dataStr}`,
});
}
[代码]
搞定,这样我们一个[代码]router.push[代码]就能自动切换两种跳转方式了,而且之后一旦页面类型有变动,我们也只需要修改[代码]route[代码]的定义就可以了。
直接寻址
alias用着很不错,但是有一点挺麻烦得就是每新建一个页面都要写一个alias,即使没有别名的需要,我们是不是可以处理一下,如果在alias没命中,那就直接把name转化成url?这也是阔以的。
[代码]function push({ name, data }) {
const dataStr = encodeURIComponent(JSON.stringify(data));
const route = routes[name];
const url = route ? route.path : name;
if (route.type === 'tab') {
wx.switchTab({
url: `${url}`, // 注意tab页面是不支持传参的
});
return;
}
wx.navigateTo({
url: `${url}?encodedData=${dataStr}`,
});
}
[代码]
在页面中使用:
[代码]Page({
onLoad(options) {
router.push({
name: 'pages/user_center/a/index',
data: {
isActive: true,
},
});
},
});
[代码]
注意,为了方便维护,我们规定了每个页面都必须存放在一个特定的文件夹,一个文件夹的当前路径下只能存在一个index页面,比如[代码]pages/index[代码]下面会存放[代码]pages/index/index.js[代码]、[代码]pages/index/index.wxml[代码]、[代码]pages/index/index.wxss[代码]、[代码]pages/index/index.json[代码],这时候你就不能继续在这个文件夹根路径存放另外一个页面,而必须是新建一个文件夹来存放,比如[代码]pages/index/pageB/index.js[代码]、[代码]pages/index/pageB/index.wxml[代码]、[代码]pages/index/pageB/index.wxss[代码]、[代码]pages/index/pageB/index.json[代码]。
这样是能实现功能,但是这个name怎么看都跟alias风格差太多,我们试着定义一套转化规则,让直接寻址的name与alias风格统一一些,[代码]pages[代码]和[代码]index[代码]其实我们可以省略掉,[代码]/[代码]我们可以用[代码].[代码]来替换,那么原来的name就变成了[代码]user_center.a[代码]:
[代码]Page({
onLoad(options) {
router.push({
name: 'user_center.a',
data: {
isActive: true,
},
});
},
});
[代码]
我们再来改进[代码]router/index.js[代码]中[代码]push[代码]的实现:
[代码]function push({ name, data }) {
const dataStr = encodeURIComponent(JSON.stringify(data));
const route = routes[name];
const url = route ? route.path : `pages/${name.replace(/\./g, '/')}/index`;
if (route.type === 'tab') {
wx.switchTab({
url: `${url}`, // 注意tab页面是不支持传参的
});
return;
}
wx.navigateTo({
url: `${url}?encodedData=${dataStr}`,
});
}
[代码]
这样一来,由于支持直接寻址,跳转home和uc还可以写成这样:
[代码]router.push({
name: 'index', // => /pages/index/index
});
router.push({
name: 'user_center', // => /pages/user_center/index
});
[代码]
这样一来,除了一些tab页面以及特定的路由需要写alias之外,我们也不需要新增一个页面就写一条alias这么麻烦了。
其他
除了上面介绍的navigateTo和switchTab外,其实还有[代码]wx.redirectTo[代码]、[代码]wx.navigateBack[代码]以及[代码]wx.reLaunch[代码]等,我们也可以做一层封装,过程雷同,所以我们就不再一个个介绍,这里贴一下最终简化后的api以及原生api的映射关系:
[代码]router.push => wx.navigateTo
router.replace => wx.redirectTo
router.pop => wx.navigateBack
router.relaunch => wx.reLaunch
[代码]
最终实现已经在发布在github上,感兴趣的朋友可以移步了解:mp-router。