Interface preloading of WeChat applet

I am participating in the "Nuggets·Starting Plan"

Before, the technical director asked me to study the preloading scheme of the WeChat applet request, but later because of other development tasks, the preloading was put on hold. Recently, the development task is not heavy, and I happened to read related articles, learned about the implementation principle, and wrote a wheel by myself. Please read on.

Mini Program Official Solution

In the performance and experience chapter of the applet document , there is already a solution for requesting preloading, which is to use the EventChannel page communication method to request the data of the next page in advance on the current page, and then pass it to the next page through the EventChannel. When the loading starts, the data is obtained and rendered directly.

personal plan

Because before seeing the pre-loaded documents on the WeChat interface, and according to the WeChat solution, the following pain points must be solved

  • Page A jumps to page B, and page B needs to preload the request. I don't want to write it on page A
  • If page A has already requested the interface of page B, but jumps to page B before the interface result comes back, I don’t want to send the request network again at this time
  • The problem of subcontracting (this is described in detail below)

Understanding the loading process of WeChat is the basis for implementing the preloading solution. When WeChat starts loading, it will first load the main package, and loading the main package will require (path) all the pages in the main package. In the process of require, the page function will be called, but the page instance has not been created at this time, so other pages except the home page cannot be viewed in the route.

Look at the picture, when the applet starts, after all the babel and related resources are loaded, a script will be created, and the content of the script is to load the main content, the second picture. (Personal understanding, if there is any error, please point out in the comments, thank you)

Next, we can start typing the code. First, let's make an agreement that we need to add two additional configurations to the pages that require preloading requests, as follows:

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

Then rewrite the page method of the applet

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()

Page A can decide the timing of preloading by itself. You can call this.preload (page B path, data) method at any time to trigger the network request of page B, or you can use this.preload(page Bpath,data) method to trigger the page B's For network requests, you can also use this.p re l o a d ( page Bp a t h ,The d a t a ) method triggers the network request of page B , or you can use the t hi s . route(page Bpath,data,true) method, which is called after the jump is successful.

For the three pain points mentioned above, the solution is as follows

pain point 1

Use the applet loading process to cache the registerPreload method of all pages, and use the path to find the cache method to call when page A is called.

pain point 2

Because the request is asynchronous, it may arrive at page B, but the preloaded request has not come back yet. So I customized a this.$setState method to replace this.setData, and used it in the registerPreload method. When the page B is not created, the Data will be saved in the cache. After the creation is completed, this.setData will be called directly to update the page . At the same time, in the page onLoad method of page B, it will judge whether the registerPreload method is called. If it is called, it will check whether the requested data is used in the memory, and if so, it will setData to data.

pass point 3

If page B is in a subpackage, when page A calls the registerPreload method of page B, the registerPreload method has not been registered in the cache. The solution is to check whether the cache has been registered at the time of calling. If it is not registered, the request to call registerPreload will be cached. When registerPreload is registered to the cache when the subcontracted code loading page is registered, check whether there is a registerPreload request for the current page. If so, call it immediately .

The source code above is my personal understanding and application of pre-loading requests for WeChat applets. You are welcome to leave a message in the comment area for guidance and communication.

Guess you like

Origin blog.csdn.net/qq_42944436/article/details/127675569