You are on page 1of 37

支付宝小程序性能优化实践

伊不
CONTENTS 1 原理介绍 走进支付宝小程序底层架构

目录 2 更快启动 为首屏渲染争分夺秒

3 更快呈现 打开即可见的视觉体验

4 更快交互 富交互场景能力增强
Part 1 原理介绍
走进支付宝小程序底层架构
H5 代码是如何渲染出视图的?
class Component extends React.Component {
constructor() {
super();
this.state = { year: 0 }; 数据变更
}
render() {
return ( 组件树维护 DOM视图刷新
<div onClick={this.handleTap.bind(this)}>
{this.props.name}{this.state.year}
</div>
);
}
事件回调
handleTap() {
window.alert('Welcome to SEE Conf 2021');
}
生命周期
componentDidMount() {
this.setState({ year: 2021 });
}
}

React 组件
数据变更
WebView 内部 data 或 外部 props 发生改变

组件树维护
数据变更 调度渲染逻辑,收集组件创建/更新/卸载信息
组件树维护

DOM视图刷新 DOM 视图刷新


根据模板将数据渲染成真实的 UI 界面
生命周期

生命周期调度
界面交互 触发组件的生命周期方法

事件回调
事件回调
界面发生交互事件时,触发组件绑定的事件回调
支付宝小程序代码是如何渲染出视图的?
class Component extends React.Component { <view onTap="handleTap">
constructor() { {{name}}{{year}}
组件树维护 DOM视图刷新
super(); </view>
this.state = { year: 0 };
}
render() { Component({
data: { year: 0 }, 数据变更
return (
<div onClick={this.handleTap.bind(this)}> props: { name: '' },
{this.props.name}{this.state.year} methods: {
</div> handleTap() { 事件回调
); my.alert({
} message: 'Welcome to SEE Conf 2021',
handleTap() { });
window.alert('Welcome to SEE Conf 2021'); },
} },
生命周期
componentDidMount() { didMount() {
this.setState({ year: 2021 }); this.setData({ year: 2021 });
} },
} });

React 组件代码 支付宝小程序组件代码


WebView WebView Worker

数据变更
数据变更
组件树维护
组件树维护
DOM视图刷新
DOM视图刷新
生命周期
生命周期

界面交互
界面交互
事件回调
事件回调

H5 单线程模型 支付宝小程序极简双线程模型
WebView 和 Worker 如何交互?
WebView Worker
const { serviceWorker } = window.navigator;
await serviceWorker.register( self.addEventListener(
启动 'message',
'./worker.js',
{ scope: './' }, ({ data, ports }) => {
); 启动 switch (data.type) {
case 'WebViewInit':
const { active } = await serviceWorker.ready; 启动成功回调 const port2 = ports[0];
const { port1, port2 } = new MessageChannel(); }
active.postMessage({ },
建立通道 );
type: 'WebViewInit',
pagePath, 建连成功
queryString,
}, [port2]);
数据变更

组件树维护
// 接收来⾃ WebView 的消息的回调
// 接收来⾃ Worker 的消息的回调 DOM视图刷新
port1.onmessage = (event) => { port2.onmessage = (event) => {
// ... // ...
};
生命周期 };
// 向 Worker 发送消息的⽅法 // 向 WebView 发送消息的⽅法
port1.postMessage; 界面交互 port2.postMessage;

事件回调
Part 2 更快启动
为首屏渲染争分夺秒
性能优化前后对比视频
1 2 3 4 5
830ms 1250ms 1320ms 2090ms 2750ms

700ms 1400ms 1820ms

3 4 5
白屏等待
1 2 3 首次渲染耗时较长
830ms 1250ms 1320ms

700ms 页面抖动
1秒内页面抖动3次

3
1 Worker 启动
依赖 WebView 拉起
1 2 3
830ms 1250ms 1320ms
2 页面的初始数据
依赖 WebView 建连
组 组 组
建 视 视 视
件 件 件
启 立 图 图 图 生命周期调度
1 2 树 3 树 3 树 3
动 通 刷 刷 刷
维 维 维 依赖 WebView 渲染
道 新 新 新
护 护 护

建 数 生 数 生 数
启 连 据 命 据 命 据
动 成 变 周 变 周 变
功 更 期 更 期 更
启动入口
Worker 通过 WebView 拉起,这种串行依赖使得
Worker 启动稍慢,同时导致 WebView 建连也得等
待 Worker 启动结束。二者相互阻塞

数据初始化源

WebView 过度职责 Worker 得等和 WebView 建连之后才能获得页面的


初始化信息,使得业务逻辑执行靠后,不能够提前
准备数据请求等

组件树维护方
组件树在 WebView 侧维护,数据变更后,Worker
要调度生命周期来准备下一轮数据变更,必须等待
一次通信来回和视图渲染,导致 WebView 侧页面抖
动和 Worker 侧逻辑阻塞
启动入口
使用 v8 Worker 替换 serviceWorker。客户端在启动
WebView 的同时,也拉起 v8 Worker,确保二者并
行启动,不再相互阻塞

数据初始化源
启动 v8 Worker 时,直接将页面的初始化信息注
v8 Worker
入,不必等 WebView 的建连,Worker 可以提前执
行页面的逻辑代码。真正建连一到,可以立即开始
渲染
WebView Worker WebView Worker

数据变更 数据变更

组件树维护 组件树维护

DOM视图刷新 生命周期

生命周期 数据不再变更
DOM视图刷新

组件树维护在 WebView 侧 组件树维护在 Worker 侧


视图渲染器
轻量WebView 接收数据渲染视图,将视图信息/事件发回
WebView Worker WebView Worker

启动 启动 启动

启动 启动成功回调 数据变更

启动成功回调 建立通道 组件树维护

建立通道 生命周期

建连成功 数据不再变更

数据变更 DOM视图刷新

组件树维护

DOM视图刷新

生命周期

界面交互 界面交互

事件回调 事件回调
Part 3 更快呈现
打开即可见的使用体验
1 2 3
830ms 1250ms 1320ms

700ms
1320ms

700ms

3
白屏时间还是很长啊!
服务端渲染
H5 Server
Server Side Rendering

WebView

小程序 Client ?
安卓支付宝客户端扫码体验
首次访问会出现加载态
重启 App 后体验极速启动
唤起 WebView 小程序默认加载态 业务自定义骨架屏 正常内容

正常启动⽀付宝⼩程序
唤起 WebView 快照缓存 正常内容

携带快照启动⽀付宝⼩程序
提前渲染
在获得 Worker 初始数据之前渲染好不变内容

Snapshot 优化体验
告别加载态、骨架屏,减少用户焦虑
将真实页面内容作为唤起时的骨架屏

加速决策
用户看到页面后,可以立即决策行为
WebView Worker

启动 启动

展示快照 开启数据等待

开启数据队列 数据变更

组件树维护

生命周期

数据不再变更
攒入数据队列

异步数据返回

通知数据完备
消费所有数据

DOM视图刷新
Part 4 更快交互
富交互场景能力增强
WebView Worker WebView

界面交互 界面交互

事件回调 事件回调

数据变更 DOM视图刷新

生命周期

数据不再变更
DOM视图刷新

来回通信带来性能开销和视图卡顿 H5 开发的事件流程
支付宝小程序 SJS 事件回调流程
function handleTap(event, ownerComponent) {
event.instance.addClass('hello-world');
}

function toTimeString(timeStamp) {
return getDate(timeStamp).toTimeString();
}

Safe JavaScript export default {


handleTap,
被允许在 WebView 中运行的有限脚本 toTimeString,
}

<import-sjs from="./util.sjs" name="sjs" />

<view onTap="{{ sjs.handleTap }}">


{{sjs.toTimeString(timeStamp)}}
</view>
彩蛋 更快开发
预览 HMR 带来的开发体验改进
THANKS!

You might also like