codecamp

百度智能小程序 提前请求主数据

在 onInit 请求首屏主数据

基础库 3.160.12 及以上版本开始支持Page.onInit。

大部分小程序,需要发起至少一次网络请求并调用setData,才能完成整个页面的最终渲染。如果能优化该环节,页面的渲染时间将会大大缩短。

回顾一下我们在性能优化的原理和手段中所介绍的小程序启动流程,可以发现无论是把主数据请求放在App.onLaunch里还是Page.onLoad里,都会存在一些难以解决问题:

  1. 如果在App.onLaunch中请求主数据,那么每个页面的请求逻辑都需要在放在App的生命周期中,这样不仅造成了逻辑的耦合,也将一定程度影响初始数据initData的发送,继而拖慢渲染层的初次渲染。
  2. 如果在Page.onLoad中请求主数据,那么必须要等到渲染层完成firstRender之后才能请求主数据,时机比较晚。

onInit 简介

小程序提供一种页面级别的生命周期Page.onInit。

该生命周期的执行时机介于App.onLaunch和Page.onLoad之间。具体的执行时机可参考下图,小程序是在setInitData之后立即执行Page.onInit()。

如果把主数据请求从 Page.onLoad 转移到 Page.onInit 中,将极大提升小程序的页面加载性能。

开发者可以在onInit中向服务器请求数据,并执行setData。图中展示了setData的两种时机,同时应注意:

  1. 如果开发者setData发出的时机早于渲染线程的 FCP ,那么在onLoad中将能获取到本次setData的视图信息。
  2. 如果setData晚于 FCP ,那么onLoad中将获取不到本次setData的视图信息。
由于App.onShow是依赖客户端通知逻辑线程的,所以Page.onInit不一定会在首次 App.onShow之后执行。可确定的前后顺序是:App.onLaunch-> Page.onInit ->Component.created->Component.attached->Page.onLoad

如何使用 onInit

尽量将页面的业务数据请求放在 Page.onInit 中。

function getData(param) {
	return new Promise((resolve, reject) => {
		swan.request({
            url: 'xxx',
            success: res => resolve(res)
        });
	});
}
Page(
    // 使用一个标记位,确保只请求一次主数据
	hasRequest: false
	data: {
       value: ''
    },
	onInit(param) {
		if (!this.hasRequest) {
			this.hasRequest = true;
			getData(param).then(res => {
                this.setData({
                    value: res.data
                });
            })
		}
	},
	onLoad(param) {
		if (!this.hasRequest) {
			this.hasRequest = true;
			getData(param).then(res => {
                this.setData({
                    value: res.data
                });
            })
		}
	}
);

使用建议

  1. 不能进行任何依赖视图层的操作,包括且不限于:selectComponentselectAllComponentsswan.createSelectorQueryswan.createMapContextswan.createCameraContext、swan.createCanvasContext等;
  2. 由于并非所有版本的基础库都支持此生命周期,开发者可以参考上述代码片段,增加兼容逻辑;
  3. 如果您的小程序在逻辑线程初始化阶段存在较大瓶颈,那么使用Page.onInit可能不会有明显效果。建议从减少动态库和插件的使用、减少App.onLaunch耗时等角度进行优化。

onInit 的收益

此处以百度知道、百度百科和宝宝知道小程序为例使用Page.onInit进行了优化,上屏时长均有明显提升。

以下是三个小程序把主数据请求从 Page.onLoad 迁移到 Page.onInit 后,获取到的收益:

小程序 收益(单位 ms)
百度知道 210
百度百科 100
宝宝知道 150



prelink 预连接

百度App 11.26及以上版本开始支持prelink

prelink 简介

小程序首屏的渲染,通常依赖某个网络请求返回的数据,我们将这个请求定义为主请求。大部分情况下,主请求的速度提升,渲染速度也会随之提升。所以,小程序框架提供了prelink机制,用于提前和业务服务器建立网络连接,使随后的主请求可以复用该连接,提升请求速度。

  • 使用prelink前后对比 配置prelink前后对比

通过上面的对比可以看出,在配置了 prelink 时,框架会在加载业务代码 app.js 的同时发送一个预连接请求。在发送主请求时,可以直接复用该连接,达到减少主请求时长的目的。


如何使用 prelink

正确使用 prelink,需要做以下两个操作:

1.在开发者平台配置 prelink 信息

  • 进入开发者平台,在【开发管理】的【设置】点击【开发设置】。 配置1
  • 找到【服务器域名】,点击【修改】。 配置2
  • 在弹出的窗口中,找到【 prelink 的合法地址】,输入 prelink 地址后点击【保存并提交】。 配置3
  • 该接口的域名需要和主请求域名保持一致。
  • 支持配置多个预连接接口,对不同域名发起预连接。
  • 该接口需要支持HEAD请求,其他规则(如域名校验、UA、REFERER 等)和 request 保持一致。
  • 该接口的响应速度和稳定性均会影响 prelink 的效果,为了尽可能的减少预连接请求的响应时长,建议配置静态接口,并保证接口的稳定性。

2.在小程序业务代码中,尽可能前置需要复用预创建连接的请求(一般为主请求)。


如何测试 prelink

在百度App 中,提供了 prelink 的测试工具,用于开发者验证配置的正确性,开发者可按下图中的流程进行测试验证。

preklink测试方法



数据前置获取的优势

通过上文的 onInit,我们可以在大部分场景下,让主数据请求的时机更早,但在一些场景下,主数据的请求可能需要更早,或是主数据请求依赖其他一些需要耗时获取的数据作为参数,比如在一些商业线索类页面,商家会希望用户进入页面时立即看到线索相关信息,而不需要再等待,这种时候,我们需要一些能够更早获取数据的机制。本节,我们将介绍如何进行数据前置获取的优化手段。

什么是数据前置获取

“数据前置获取”,就是把关键页面数据获取的时机提前至前一个页面,当用户进入这些关键页面后,不再从后端请求数据,而是直接渲染从上一页面中获取到的数据。

通常情况下,用户看到一个有效页面的流程是:点击页面入口 → 进入页面 → 请求页面的数据 → 渲染请求到的数据。如果频繁访问某个页面的话,页面的数据也需要反复请求,不仅网络开销大,而且用户每次进入页面,都需要等待网络请求数据时间,用户体验较差。

通过数据前置获取,可以把关键页面的数据放在入口页面获取,页面跳转至关键页面时,将数据以页面参数的形式传输给落地页。这时候,用户看到关键页面的流程就变成了:点击页面入口 → 进入关键页面 → 渲染传输过来的数据。

不使用数据前置获取的用户体验

用户需要等待网络请求,一般约 200ms--400ms 左右:

使用数据前置获取的用户体验

关键页面直接获取前一页面拿到的数据,然后渲染数据,可以节省 200ms--400ms 的网络时间:

数据前置获取的优化方案示例

如果跳转的新页面页数据量较小(字段总数不超过 15 个、字段层级不超过 3 层),可以把数据放在跳转参数中传递到下一页:

  1. 在调用 swan.navigateTo 跳转到落地页时,将落地页所需的数据拼接在跳转 url 参数中。
    toNextPage(pageData) {
       swan.navigateTo({
             url: '/pages/to/next/path?pageData=' + encodeURIComponent(pageData)
       });
    }
    
  2. 新页面在 onload 时,从 option 参数中取数据,并解析、渲染。
    onLoad(option) {
       let pageData = option.pageData;
       // 转入对应的数据处理函数中
       if (pageData) {
          this.setPageData(pageData);
       }
    }
    

如果跳转的新页面据量较大(字段总数在 15 个以上、字段层级超过 3 层),数据就不合适放在跳转参数中传递,可以放进全局变量中:

  1. 在调用 swan.navigateTo 跳转到落地页前,将落地页所需的数据存在全局变量中
    toNextPage(pageData) {
       // 这里使用到的pageData全局变量,需要在app.js中定义
       getApp().globalData.pageData = pageData;
       swan.navigateTo({
             url: '/pages/to/next/path'
       });
    }
    
  2. 新页面在 onload 时,从全局变量里获取页面所需的数据
    onLoad(option) {
       if (getApp().globalData.pageData) {
          let pageData = getApp().globalData.pageData;
          // 转入对应的数据处理函数中
          this.setPageData(pageData);
          // 清空全局变量
          getApp().globalData.pageData = null;
       }
    }
    

使用注意事项

“关键页数据前置获取”,本质上是把关键页的数据获取成本转移到了入口页,所以并非所有的页面都可以使用。如要使用该手段优化某些页面,则页面必须满足如下条件:

  1. 数据较简单、数据量少(字段总数不超过 15 个、字段层级不超过 3 层);
  2. 该页面访问的频次较高,存在退出该页面后重新进入的情况;


数据持久化

在智能小程序中,我们把:从服务端获取到的数据,通过swan.setStorage方法保存在本地,页面渲染时直接从本地获取所需数据的这一行为叫做“数据持久化”。

为什么要把页面数据持久化

在包含了:固定 banner、筛选、导航等这些低时效性数据的页面里,把这些低时效性的数据存在本地,页面加载时直接从本地读取、渲染数据,会使页面上的留白时间大大减少,极大的提升用户体验,对白屏率指标也将有宜。

注意:对于存放在本地的数据,并非永远不再更新。而是在请求当前页面其他数据时,拿到最新的要持久化的数据,并将新数据替换进本地,供下次加载使用。

<

未使用数据缓存的用户体验

使用数据缓存的用户体验

<

数据持久化的优化方案示例

  1. 定义存、取本地数据的工具方法:

    从本地获取数据的方法:

    function getLocalData(key) {
        return new Promise((resolve, reject) => {
            let group = 'homeData'; // 数据存储的组,开发者可按照页面进行分组,防止不同页面的数据互相覆盖
            swan.getStorage({
                key: group + '_' + key,
                success: res => {
                    // console.log('get', res);
                    let localData = res.data;
                    resolve(localData);
                },
                fail: err => {
                    console.error('读取失败', err);
                    reject(err);
                }
            });
        });
    }
    module.exports.getLocalData = getLocalData;
    

    向本地存储数据的方法:

    function setLocalData (data) {
        return new Promise((resolve, reject) => {
            let group = 'homeData'; // 数据存储的组,开发者可按照页面进行分组,,防止不同页面的数据互相覆盖
            let keys = Object.keys(data);
            keys.forEach(item => {
                swan.setStorage({
                    key: group + '_' + item,
                    data: data[item],
                    success: res => {
                        resolve(res);
                    },
                    fail: err => {
                        console.error(err);
                        reject(err);
                    }
                });
            });
        });
    }
    module.exports.setLocalData = setLocalData;
    
  2. 在页面onload时,调用getLocalData方法,从本地取数据
    const utils = require('./lib'); // 引入存取本地数据的公共方法
    Page({
        onload: function() {
            // 从缓存中获取指定的数据
            utils.getLocalData('diamondzone').then(diamondzone => {
                // 把获取到的数据,传入对应的数据处理函数中
                if (diamondzone) {
                    this.setDiamondzoneData(diamondzone);
                }
            });
        }
    })
    
  3. 在页面 request 方法的 success 回调中,调用setLocalData方法把新数据替换到本地
    const utils = require('./lib');  // 引入存取本地数据的公共方法
    Page({
        request(path, params) {
            swan.request({
                url: path,  // 接口地址
                data: params,   // 接口参数
                success: res => {
                    utils.setLocalData(res.diamondzone);    // 将从后端获取到的最新数据存进本地
                }
            })
        }
    })
    

使用注意事项

  1. 本地存储的数据总量不宜过大,建议维持在 1M 以内;
  2. “数据持久化”仅限于对时效性没有要求的数据使用,对于时效要求高的数据,不应存储在本地;


百度智能小程序 加速initData的生成
百度智能小程序 如何优化渲染层速度
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

百度智能小程序开发文档

百度智能小程序 组件

百度智能小程序 地图

百度智能小程序 画布

百度智能小程序 API

百度智能小程序 界面

百度智能小程序 关注小程序引导组件

百度智能小程序 自定义组件

百度智能小程序 媒体

百度智能小程序 设备

百度智能小程序 拨打电话

百度智能小程序 内存警报

百度智能小程序 手机联系人

百度智能小程序 用户截屏事件

百度智能小程序 第三方平台

百度智能小程序 开放接口

百度智能小程序 百度收银支付

百度智能小程序 分包预下载

百度智能小程序 数据分析

百度智能小程序 服务端

百度智能小程序 云开发

百度智能小程序 初始化

百度智能小程序 云函数

百度智能小程序 服务端初始化

百度智能小程序 服务器获取上下文

百度智能小程序 服务端云函数

百度智能小程序 开发教程

百度智能小程序 功能开发

百度智能小程序 基本原理

百度智能小程序 小程序自动化

百度智能小程序 视频教程

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }