微信小程序之接口预加载
之前技术主管让我研究一下微信小程序的请求预加载的方案,但后来因为别的开发任务,预加载这款就搁置下来了。最近开发任务不重,又凑巧看了相关的文章,了解了实现原理,自己写了个轮子,大家请往下看。
<hr style="display: none;"> <h2 style="display: none;">theme: awesomeGreen</h2> <p>之前技术主管让我研究一下微信小程序的请求预加载的方案,但后来因为别的开发任务,预加载这款就搁置下来了。最近开发任务不重,又凑巧看了相关的文章,了解了实现原理,自己写了个轮子,大家请往下看。</p> <h2>小程序官方方案</h2> <p>在小程序文档的<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips/runtime_nav.html#_2-3-%E6%8F%90%E5%89%8D%E5%8F%91%E8%B5%B7%E6%95%B0%E6%8D%AE%E8%AF%B7%E6%B1%82">性能与体验</a>章节,已经有了请求预加载的方案,就是利用 <a href="https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#%E9%A1%B5%E9%9D%A2%E9%97%B4%E9%80%9A%E4%BF%A1">EventChannel</a>页面通信的方法,在当前页面提前请求下个页面的数据,然后通过EventChannel传递到下个页面,在下个页面开始加载的时候获取到数据,直接渲染。</p> <h2>个人方案</h2> <p>因为自己实现在看到微信接口预加载文档之前,并且按照微信方案同样要解决以下几个<strong>痛点</strong></p> <ul> <li>页面A跳页面B,页面B需要预加载的请求我不想在页面A写</li> <li>如果页面A已经请求了页面B的接口,但接口结果还没回来就跳转到了页面B,这个时候我也不想再次发送请求网络</li> <li>分包的问题(这个下面详细说)</li> </ul> <p>了解微信的加载流程是实现预加载方案的基础。微信启动加载时候,首先会先加载主包,加载主包会把主包内的页面全部都require(path)进来。在require的过程,page函数会调用,但这时候的页面实例还没有创建,所以路由里是查看不到除主页的其他页面的。</p> <p>看图片,小程序启动中,在所有的babel和相关资源加载完毕,会创建一个script,script的内容就是加载主内容,第二张图。(个人理解,如有差错,评论请指出,谢谢)</p> <p><img src="http://cdn-dev.hanshanpeng.com/images/98572-1718355006880?imageView2/0/format/webp/interlace/1/q/75" alt="0df9f63ff33f44fe95076ea1da8a84ae_tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp"></p> <p><img src="http://cdn-dev.hanshanpeng.com/images/72100-1718355012116?imageView2/0/format/webp/interlace/1/q/75" alt="d8022173c72246a7af0ec9303edb885c_tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp"></p> <p>下面我们就可以开始敲代码了,首先我们先约定一下,在需要预加载请求的页面我们要加入额外两个配置,如下:</p> <pre><code class="language-javascript">page({ path:"xxx/xxx/xxx", //要和app.json中注册的路径一样 registerPreload(){},//这里写需要预加载的请求 }) </code></pre> <p>然后要重写小程序的page方法</p> <pre><code>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 &amp;&amp; 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]){ allPagePreloadMapurl; }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() </code></pre>

<p>页面A可以自己决定预加载的时机,可以选择任意时候调用this.$preload(页面Bpath,data)方法触发页面B的网络请求,也可以使用this.$route(页面Bpath,data,true)方法,在跳转成功后调用。</p> <p>针对上面提的三个痛点,解决方法如下</p> <h3>痛点1</h3> <p>利用小程序加载过程,缓存所有页面的registerPreload的方法,在页面A调用时候,利用path查找缓存的方法进行调用。</p> <h3>痛点2</h3> <p>因为请求是异步的,可能到了页面B,预加载的请求还没有回来。所以自定义了一个this.$setState方法替换this.setData,并在 registerPreload 方法中使用,在页面B未创建的时候会将Data保存才缓存中,创建完成后,会直接调用this.setData更新到页面。同时在页面B的页面onLoad方法中,会判断 registerPreload 方法是否调用,如果调用,会检查内存中使用是否有请求的数据,有的话会setData到data。</p> <h3>通点3</h3> <p>如果页面B位于分包中,在页面A调用页面B的 registerPreload 方法时候,registerPreload 方法还没有注册到缓存中。解决方案是在调用时候检查缓存中是否已经注册,如果未注册,则缓存调用 registerPreload 的请求,在分包代码加载 页面 registerPreload 注册到缓存时候,检查是否有当前页面的 registerPreload 请求,如果有则立马调用。</p> <p><a href="https://github.com/hspprogrammer/wx-gulp-mini">源码</a>以上都是个人对微信小程序请求预加载的粗浅理解和应用,欢迎各位大佬评论区留言指导交流。</p><!--14-->
09:36 AM
status-bar_img
status-bar_img
一名什锦区博主、程序猿、业余翻唱,我将在此记录学习过程中的笔记、生活中的点滴,也分享一些经验与想法。在这与你相遇很幸运💛,我若化作一首永恒的诗,你是否会一直细细品味?
邮箱githubqq微博