内容概要
利用微信小程序的路由堆栈信息解决小程序内Page间的数据(或事件)传递的问题。通过对各种方案的对比、分析、总结,得出我们的升华版解决方案,满足你的不满足。
场景再现
工作中我们经常会遇到B页面需要A页面内的部分或全部数据;C页面内的一个函数执行完之后需要改变B页面内的显示样式;也或者是A和B两个页面用到了同样的网络数据,在其中一个页面做出修改后另一个页面也要随之改变以保证回传服务器时数据的准确性,等等诸如此类的页面间数据传递的问题。
现有方案梳理
当然针对上面场景中各种问题我们可以有很多种的解决方案。例如利用微信API中reLaunch、redirectTo、navigateTo 函数的url路径携带参数到目标页面;利用EventChannel信道实现打开页面与被打开页面通信;利用全局文件共享数据;利用路由堆栈获取目标页面实例等等,我目前用过的方案就以上四种,如果你用过其他方案也可以提出来我们一起讨论学习。接下来我们就针对以上四种方案进行一个简略的分析。
方案一:reLaunch、redirectTo、navigateTo 函数url路径携带参数到目标页面
这种方案大家应该都用过,在页面跳转过程中传递一些简单的数据还是十分方便的,但是它的缺点也很明显,在五个路由函数中只有三个可以在跳转路径上携带参数,而且参数不可以是对象类型,在遇到携带"?"等特殊字符的参数还需要进行转码操作。对于跨多个页面的数据传递比较繁琐。归纳如下:
- 优点:操作简单方便。
- 缺点:单项传递;复杂数据传递局限性较大;跨多页面数据传递繁琐;只能在reLaunch、redirectTo、navigateTo
三个函数中使用。 - 适用情况:页面间简单数据的单项传递。
方案二:EventChannel信道实现打开页面与被打开页面通信
从基础库2.7.3开始支持EventChannel。开发者可以通过navigateTo路由函数在页面跳转切换过程中自定义两个页面的数据交互函数。至于EventChannel自定义规则可参考微信API中的 navigateTo
函数。该方案解决了方案一中的复杂数据传递的局限性。可是它仅限于navigateTo函数中使用,并且在跨多页面传递时操作起来复杂性仍然很高。归纳如下:
- 优点:使用灵活性较高;可传递较复杂数据;可双向传递;
- 缺点:跨多页面数据传递繁琐;只能在navigateTo一个函数中使用。
- 适用情况:由navigateTo控制路由的两个页面间数据传递。
方案三:全局文件共享数据
全局共享数据无非就是定义一份谁都可以使用和修改的数据。这个方案很简单,而且很轻松的解决了方案一和方案二中的跨多个页面的数据传递问题。可是数据处理的及时性却大打折扣,只能期待各个页面自己触发自身的事件。归纳如下:
- 优点:实现简单,可跨多页面。
- 缺点:及时性欠缺,灵活度不够。
- 适用情况:不要求事件的及时性和功能比较集中的模块。
方案四:利用路由堆栈获取目标页面实例
兵法中常说“擒贼先擒王”,如果我们拿到了某个页面的实例索引,那就相当于是在战场上控制了敌方的将领,我们说要粮草他就得给粮草,我们说要兵器他就得乖乖的给兵器。所以该方案我们也可以戏称为“擒王方案”。如果我们给“擒王方案”做一个归纳的话,应该是这样的:
- 优点:灵活性/及时性高;数据类型不限;可跨多页面使用;
- 缺点:代码重复性较高;不在堆栈内的页面无法进行操作;
- 适用情况: 确定页面实例在堆栈内的交互性比较强多页面。
提炼升华
在上一节中我们对四个方案进行了一下简单的梳理,每个方案也各有优缺点,上述四种方案可能已经满足了我们工作中的使用,可是作为程序员的我们不应该停下追逐更优更好的脚步。我们尽量把上述方案的优点集中起来,并且规避掉缺点。整理出一个相对完善的方案。首先自定义一个跨页面(当然也可在页面内使用)的事件处理类,暂时命名其为funbus。具体的处理逻辑如下:
一:定义全局事件缓存Map。
// 事件缓存
const events = {};
二:根据 getCurrentPages() 函数获取被操作页面实例。
/**
* 同步执行,会立即执行并拿到被执行函数的返回结果
*
* pagePath 页面名称或路径
* method 执行的方法名
* params 方法参数
*/
function callFun(pagePath, method, params) {
let pages = getCurrentPages();
let page = null;
for (let i = 0; i < pages.length; i++) {
if (pages[i].route.indexOf(pagePath) > -1) {
page = pages[i];
break;
}
}
if (page) {
try {
return page[method](...params);
} catch (err) {
console.error('FunBus Error: ', err);
return null;
}
}
return null;
}
三:当然我们也需要处理如果被操作的页面不在堆栈内的情况。
1.事件注册和解绑。
/**
* 注册事件
*
* key 值命名规则 页面名称-描述 (如: index-refresh)
* event 是一个对象: {path: '被注册事件发生的页面路径', method: '被注册的方法名称', params: [...被注册的方法需要的参数]}
*
* 调用范例: subscribe("index-refresh", {path:"pages/index", method:"refreshPage", params: [1,2,3]});
*/
function subscribe(key, event) {
events[key] = event;
}
/**
* 解除绑定
*/
function unSubscribe(key) {
delete events[key];
}
2.之前未在堆栈内的页面加入到堆栈时(即展示到前台时)在适当时机触发之前缓存的事件。
/**
* 唤醒/执行之前订阅的事件
*/
function notifyEvent(key, remove) {
let event = events[key];
if (event) {
// 只有remove的值是布尔类型的false时才不会移除当前事件,其他任何值该事件都会被移除
if (!remove && remove !== false) {
remove = true;
}
remove && delete events[key];
return callFun(event.path, event.method, event.params)
}
return null;
}
四:工具类的使用
1. 直接使用
let result = funbus.callFun('a/a', 'returnBpageData', [1, 2]);
this.setData({
astring: result,
});
2. 页面未在堆栈内的使用
// C页面内
// 由于B页面跳转C是重定向 redirectTo 跳过来的,所以B页面不在路由堆栈内,我们要先注册,然后再B页面里适当的时机出发该函数。
funbus.subscribe('b-changebgc', { path: 'b/b', method: 'changeBgColor', params:['yellow']});
// B页面内
// 触发在C页面注册的函数
funbus.notifyEvent('b-changebgc');
五:传送门
总结
本文大致可以分为两个重点部分。前一部分我们把页面之间进行数据传递的四种常用的方案做了一个简要的分析总结。后一部分主要是根据前一部分的优缺点整理得出一个通用的数据传递和事件处理的工具类,并对其实现和使用进行了简要说明。在文末也提供了Demo的github地址,若使用中有什么不合理不完善的地方还请不吝指出。
方案三:全局文件共享数据
全局共享数据无非就是定义一份谁都可以使用和修改的数据。这个方案很简单,而且很轻松的解决了方案一和方案二中的跨多个页面的数据传递问题。可是数据处理的及时性却大打折扣,只能期待各个页面自己触发自身的事件。
你说的及时性差具体是表现在哪?如果没有每个页面去触发,你要怎么拿数据,我就不理解为什么那本地的共享数据会及时性很差?