# 页面切换优化
页面切换的性能影响用户操作的连贯性和流畅度,是小程序运行时性能的一个重要组成部分。
# 1. 页面切换的流程
要想优化页面切换的性能,有必要先简单了解下小程序页面切换的过程。页面切换流程如图所示:
开发者可以通过
wx.getPerformance
接口中 entryType 为 navigation,name 为 route 的指标(PerformanceEntry),获取页面切换耗时时。
当切换的目标页面已加载完成时(例如:路由类型为 navigateBack,或 switchTab 到一个已加载的页面),不需要进行「视图层页面初始化」和「目标页面渲染」,「逻辑层页面初始化」也会较为简化,如下图所示:
# 1.1 触发页面切换
页面切换的流程,从用户触发页面切换开始。
触发时间对应 PerformanceEntry(route) 中的 startTime。
页面切换可能由以下几类操作触发:
- 小程序API调用:开发者根据用户操作,调用
wx.navigateTo
、wx.navigateBack
、wx.redirectTo
、wx.reLaunch
、wx.switchTab
等 API。 - 用户点击
<navigator>
组件进行页面切换。 - 用户点击原生 UI 触发:例如点击 tabBar(自定义 tabBar 除外)、点击左上角「返回首页」按钮、点击系统返回键或左滑返回等。
- 小程序热启动时自动 reLauch:小程序热启动的 B 类场景
目前后两种情况暂时未能获取准确的触发时间,PerformanceEntry(route) 的 startTime 和 navigationStart 一致。
# 1.2 加载分包(若有)
如果页面切换的目标页面在分包中,页面切换时需要下载分包,并在逻辑层注入执行分包内的 JS 代码。
小程序生命周期内,每个分包只会在逻辑层注入一次。
# 1.3 视图层页面初始化
小程序视图层的每个页面都是由独立的 WebView 渲染的,因此页面切换时需要一个新的 WebView 环境。视图层页面初始化主要会做以下事情:
- 创建 WebView
- 注入视图层的小程序基础库
- 注入主包的公共代码(独立分包除外)
- (若页面位于分包中)注入分包的公共代码
- 注入页面代码
为了降低视图层页面初始化的耗时,在页面渲染完成后,通常会进行必要的预加载供页面切换时使用。预加载主要会做以下事情:
- 创建 WebView
- 注入视图层的小程序基础库
- 注入主包的公共代码(若主包已在本地)
如果页面切换过快,或预加载的环境被回收,则需要在页面切换时重新创建环境。
如果页面切换时有预加载好的环境,可以大大降低页面切换的耗时。
当切换的目标页面已加载完成时,不需要进行本阶段。
# 1.4 逻辑层页面初始化
完成分包加载和 WebView 创建后,客户端会向基础库派发路由事件。
基础库收到事件后会进行逻辑层的页面初始化,包括触发上一个页面的 onHide
/onUnload
、页面组件树初始化、更新页面栈并生成初始数据发送到视图层,并依次触发目标的 onLoad
, onShow
生命周期。如果启用了「按需注入」,这一阶段还会注入页面代码。
基础库收到事件的时间对应 PerformanceEntry(route) 中的 navigationStart,对应 PerformanceEntry(firstRender) 的开始时间。
当切换的目标页面已加载完成时,不需进行页面组件树初始化和初始数据的发送,且不会触发目标页面的
onLoad
。
# 1.5 目标页面渲染
页面切换的目标页面不存在时,会触发页面的首次渲染。
在完成视图层代码注入,并收到逻辑层发送的初始数据后,结合从初始数据和视图层得到的页面结构和样式信息,小程序框架会进行页面渲染,并触发页面的 onReady
事件。
视图层渲染完成,触发页面 onReady 事件的时间,对应 PerformanceEntry(firstRender) 的结束时间。
当切换的目标页面已加载完成时,不需要进行本阶段。
# 1.6 页面切换动画
页面渲染完成后,客户端会进行页面切换的动画(如:从右向左推入页面)。如果页面初始化和渲染的时间超过固定时间,为避免用户以为页面无响应,页面会提前推入。
页面推入动画完成的时间,对应 PerformanceEntry(route) 的结束时间。
# 2. 如何优化页面切换
# 2.1 避免在 onHide/onUnload 执行耗时操作
页面切换时,会先调用前一个页面的 onHide 或 onUnload 生命周期,然后再进行新页面的创建和渲染。如果 onHide 和 onUnload 执行过久,可能导致页面切换的延迟。
- ✅ onHide/onUnload 中的逻辑应尽量简单,若必须要进行部分复杂逻辑,可以考虑用 setTimeout 延迟进行。
- ❌ 减少或避免在 onHide/onUnload 中执行耗时逻辑,如同步接口调用、setData 等。
# 2.2 首屏渲染优化
页面首屏渲染是页面切换耗时的重要组成部分,优化手段可以参考启动性能优化中首屏渲染优化部分。
# 2.3 提前发起数据请求
在一些对性能要求比较高的场景下,当使用 JSAPI 进行页面跳转时(例如 wx.navigateTo
),可以提前为下一个页面做一些准备工作。页面之间可以通过 EventChannel 进行通信。
例如,在页面跳转时,可以同时发起下一个页面的数据请求,而不需要等到页面 onLoad 时再进行,从而可以让用户更早的看到页面内容。尤其是在跳转到分包页面时,从发起页面跳转到页面 onLoad 之间可能有较长的时间间隔,可以加以利用。
# 2.4 控制预加载下个页面的时机
基础库 2.15.0 开始支持,仅安卓。低版本配置不生效。
如 1.3 节所述,小程序页面加载完成后,会预加载下一个页面。默认情况下,小程序框架会在当前页面 onReady 触发 200ms 后触发预加载。
在安卓上,小程序渲染层所有页面的 WebView 共享同一个线程。很多情况下,小程序的初始数据只包括了页面的大致框架,并不是完整的内容。页面主体部分需要依靠 setData 进行更新。因此,预加载下一个页面可能会阻塞当前页面的渲染,造成 setData 和用户交互出现延迟,影响用户看到页面完整内容的时机。
为了让用户能够更早看到完整的页面内容,避免预加载流程对页面加载过程的影响,开发者可以配置 handleWebviewPreload
选项,来控制预加载下个页面的时机。
handleWebviewPreload
有以下取值
- static: 默认值。在当前页面 onReady 触发 200ms 后触发预加载。
- auto: 渲染线程空闲时进行预加载。由基础库根据一段时间内 requestAnimationFrame 的触发频率算法判断。
- manual: 由开发者通过调用
wx.preloadWebview
触发。开发者可以在页面主要内容的 setData 结束后手动触发。
例如:
在 app.json 中(作用于全局控制)
{
"window": {
"handleWebviewPreload": "auto"
}
}
或在页面 JSON 文件中(只作用于单个页面)
{
"handleWebviewPreload": "manual"
}
Page({
onLoad() {
this.setData({
fullData: {}
}, () => {
// 只有配置为 manual 时需要调用
wx.preloadWebview?.()
})
}
})
如下图所示,假设某小程序主页完整内容是分了三个 setData 进行的: