• 微信小程序之接口预加载


    我正在参加「掘金·启航计划」

    之前技术主管让我研究一下微信小程序的请求预加载的方案,但后来因为别的开发任务,预加载这款就搁置下来了。最近开发任务不重,又凑巧看了相关的文章,了解了实现原理,自己写了个轮子,大家请往下看。

    小程序官方方案

    在小程序文档的性能与体验章节,已经有了请求预加载的方案,就是利用 EventChannel页面通信的方法,在当前页面提前请求下个页面的数据,然后通过EventChannel传递到下个页面,在下个页面开始加载的时候获取到数据,直接渲染。

    个人方案

    因为自己实现在看到微信接口预加载文档之前,并且按照微信方案同样要解决以下几个痛点

    • 页面A跳页面B,页面B需要预加载的请求我不想在页面A写
    • 如果页面A已经请求了页面B的接口,但接口结果还没回来就跳转到了页面B,这个时候我也不想再次发送请求网络
    • 分包的问题(这个下面详细说)

    了解微信的加载流程是实现预加载方案的基础。微信启动加载时候,首先会先加载主包,加载主包会把主包内的页面全部都require(path)进来。在require的过程,page函数会调用,但这时候的页面实例还没有创建,所以路由里是查看不到除主页的其他页面的。

    看图片,小程序启动中,在所有的babel和相关资源加载完毕,会创建一个script,script的内容就是加载主内容,第二张图。(个人理解,如有差错,评论请指出,谢谢)

    下面我们就可以开始敲代码了,首先我们先约定一下,在需要预加载请求的页面我们要加入额外两个配置,如下:

    page({
        path:"xxx/xxx/xxx", //要和app.json中注册的路径一样
        registerPreload(){}//这里写需要预加载的请求
    })
    
    • 1
    • 2
    • 3
    • 4

    然后要重写小程序的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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132

    页面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 请求,如果有则立马调用。

    源码以上都是个人对微信小程序请求预加载的粗浅理解和应用,欢迎各位大佬评论区留言指导交流。

  • 相关阅读:
    【Java基础】自增自减、关系、逻辑及三元<运算符
    如何实施组织的 360 度反馈计划
    01 MySQL优化 - 优化SQL语句
    dmesg 崩溃分析
    【操作系统】第一章:概述
    vue3 日期延后一天
    各类统计模型R语言的详细使用教程-R语言的线性回归使用教程
    利用随机森林对特征重要性进行评估(公式原理)
    HashMap集合
    你们眼睛干涩,胀痛吗?C# WPF 久坐提醒桌面小程序 - 内附 眼肌运动、远视力表高清图
  • 原文地址:https://blog.csdn.net/qq_42944436/article/details/127675569