我正在参加「掘金·启航计划」
之前技术主管让我研究一下微信小程序的请求预加载的方案,但后来因为别的开发任务,预加载这款就搁置下来了。最近开发任务不重,又凑巧看了相关的文章,了解了实现原理,自己写了个轮子,大家请往下看。
小程序官方方案
在小程序文档的性能与体验章节,已经有了请求预加载的方案,就是利用 EventChannel页面通信的方法,在当前页面提前请求下个页面的数据,然后通过EventChannel传递到下个页面,在下个页面开始加载的时候获取到数据,直接渲染。
个人方案
因为自己实现在看到微信接口预加载文档之前,并且按照微信方案同样要解决以下几个痛点
- 页面A跳页面B,页面B需要预加载的请求我不想在页面A写
- 如果页面A已经请求了页面B的接口,但接口结果还没回来就跳转到了页面B,这个时候我也不想再次发送请求网络
- 分包的问题(这个下面详细说)
了解微信的加载流程是实现预加载方案的基础。微信启动加载时候,首先会先加载主包,加载主包会把主包内的页面全部都require(path)进来。在require的过程,page函数会调用,但这时候的页面实例还没有创建,所以路由里是查看不到除主页的其他页面的。
看图片,小程序启动中,在所有的babel和相关资源加载完毕,会创建一个script,script的内容就是加载主内容,第二张图。(个人理解,如有差错,评论请指出,谢谢)
下面我们就可以开始敲代码了,首先我们先约定一下,在需要预加载请求的页面我们要加入额外两个配置,如下:
page({
path:"xxx/xxx/xxx",
registerPreload(){},
})
然后要重写小程序的page方法
const allPagePreloadMap = {};//所有页面预加载方法的集合
const allPreloadDateMap = {};//所有页面预加载数据的集合
let waitRunPreload; //等待预加载方法注册后执行
function rewritePage (){
let cPage = Page
Page = function rebuildPage(options){
if (!options['onLoad']) options['onLoad'] = function () { };
if (!options['onShow']) options['onShow'] = function () { };
if (!options['onHide']) options['onHide'] = function () { };
if (!options['onUnload']) options['onUnload'] = function () { };
const copyOnLoad = options['onLoad'];
options['onLoad'] = function(){
//当前页面是否有注册预加载函数
if(allPagePreloadMap[this.path]){
//预加载函数是否执行
if(allPreloadDateMap[this.path]){
this.setData.call(this,{...allPreloadDateMap[this.path]});
}else{
allPagePreloadMap[this.path].call(this,[...arguments]);
}
}
return copyOnLoad.apply(this,[...arguments])
}
const copyOnShow = options['onShow'];
options['onShow'] = function(){
this.$isBuild = true;
return copyOnShow.apply(this,[...arguments])
}
const copyOnHide= options['onHide'];
options['onHide'] = function(){
this.$isBuild = false;
return copyOnHide.apply(this,[...arguments])
}
const copyOnUnload = options['onUnload'];
options['onUnload'] = function(){
delete allPreloadDateMap[this.path];
return copyOnUnload.apply(this,[...arguments])
}
// 注册页面的预加载方法
if(options['registerPreload']){
// console.log('注册页面的预加载方法',options['path'])
if(!options['path']) {
console.error('注册预加载方法必须同时设置页面路径,同路由跳转url')
}else{
allPagePreloadMap[options['path']] = options['registerPreload'].bind(options);
//检查是否已经有该页面的预加载请求调用被缓存
if(waitRunPreload && waitRunPreload.path == options['path']){
waitRunPreload.run(allPagePreloadMap[options['path']])
waitRunPreload = null;
}
}
}
//封装setData,建议只在预加载方法中使用,如果页面已经创建,数据直接更新到data,否则存储到缓存中
options['$setState'] = function(){
const pageInstance = getCurrentPages()[getCurrentPages().length - 1];
if(pageInstance.route == this.path){
allPreloadDateMap[this.path] = {...allPreloadDateMap[this.path],...arguments[0]}
return pageInstance.setData.apply(pageInstance,[...arguments]);
}else{
allPreloadDateMap[this.path] = {...allPreloadDateMap[this.path],...arguments[0]}
}
}
//预加载页面数据
options['$preload'] = async function(path,data={}){
allPreloadDateMap[path] = {};
//在已经调用下个页面预加载请求但该页面所在分包未加载的情况下,缓存path和参数
if(allPagePreloadMap[path]){
allPagePreloadMap[path](data);
}else{
waitRunPreload = {
path,
run:function (fn){
return fn.call(this,data)
}
}
}
}
this.$route('pages/logs/logs',{},true)
options["$route"] = function (url,data = {},isPreload = false,options = {}){
if(isPreload){
allPreloadDateMap[url] = {};
//在已经调用下个页面预加载请求但该页面所在分包未加载的情况下,缓存path和参数
if(allPagePreloadMap[url]){
allPagePreloadMap[url](data);
}else{
waitRunPreload = {
path:url,
run:function (fn){
return fn.call(this,data)
}
}
}
}
url = '/'+url;
let query = "";
for(let key in data){
data[key] + '&' + query;
}
url = url + `${query.length?'?'+query:query}`
if(getCurrentPages().length>9){
wx.navigateTo({
url,
...options
})
}else{
wx.reLaunch({
url,
...options
})
}
}
return cPage(options);
}
}
rewritePage()
页面A可以自己决定预加载的时机,可以选择任意时候调用this.
p
r
e
l
o
a
d
(
页面
B
p
a
t
h
,
d
a
t
a
)
方法触发页面
B
的网络请求,也可以使用
t
h
i
s
.
preload(页面Bpath,data)方法触发页面B的网络请求,也可以使用this.
preload(页面Bpath,data)方法触发页面B的网络请求,也可以使用this.route(页面Bpath,data,true)方法,在跳转成功后调用。
针对上面提的三个痛点,解决方法如下
痛点1
利用小程序加载过程,缓存所有页面的registerPreload的方法,在页面A调用时候,利用path查找缓存的方法进行调用。
痛点2
因为请求是异步的,可能到了页面B,预加载的请求还没有回来。所以自定义了一个this.$setState方法替换this.setData,并在 registerPreload 方法中使用,在页面B未创建的时候会将Data保存才缓存中,创建完成后,会直接调用this.setData更新到页面。同时在页面B的页面onLoad方法中,会判断 registerPreload 方法是否调用,如果调用,会检查内存中使用是否有请求的数据,有的话会setData到data。
通点3
如果页面B位于分包中,在页面A调用页面B的 registerPreload 方法时候,registerPreload 方法还没有注册到缓存中。解决方案是在调用时候检查缓存中是否已经注册,如果未注册,则缓存调用 registerPreload 的请求,在分包代码加载 页面 registerPreload 注册到缓存时候,检查是否有当前页面的 registerPreload 请求,如果有则立马调用。
源码以上都是个人对微信小程序请求预加载的粗浅理解和应用,欢迎各位大佬评论区留言指导交流。
|