ReactLynx 渲染流水线
本文以 hello-world 项目为例,详细讲解 ReactLynx 渲染流程:
- 展示 ReactLynx 从首帧渲染到组件更新的完整执行过程
- 理解每个关键阶段在 Trace 视图中的事件分布和时序关系
- 掌握如何利用 Trace 工具精准定位渲染过程中的性能瓶颈
首帧渲染流程
首帧渲染包括下载 Bundle、加载 Bundle、绘制等阶段。
下载 Bundle
页面启动后首先会下载 Bundle。
加载 Bundle
Bundle 下载完成后进入加载阶段。
解析 Bundle
加载 Bundle 阶段首先解析了 Bundle 中的 CSS 样式表,脚本,页面配置等内容。
执行 MTS
Bundle 解析完成后,Engine 线程虚拟机会执行 MTS。
执行 BTS
与此同时,后台线程虚拟机会加载,解析和执行 BTS。
在 BTS 执行过程中,可以看到后台线程组件的创建过程。
TIP
后台线程不会阻塞 Engine 线程 Element 树的构建,Resolve,排版等流程。
构建 Element 树
构建 Element 树阶段调用 Element PAPI 将页面结构转换为 Element 树。
以 hello-world 项目为例,首帧创建了 1 个 page 节点,6 个 view 节点,2 个 image 节点,5 个 text 节点。
<page>
<view className="Background" />
<view className="App">
<view className="Banner">
<view className="Logo" bindtap={onTap}>
<image src={lynxLogo} className="Logo--lynx" />
</view>
<text className="Title">React</text>
<text className="Subtitle">on Lynx</text>
</view>
<view className="Content">
<image src={arrow} className="Arrow" />
<text className="Description">Tap the logo and have fun!</text>
<text className="Hint">
Edit<text style={{ fontStyle: "italic" }}>{" src/App.tsx "}</text>
to see updates!
</text>
</view>
<view style={{ flex: 1 }}></view>
</view>
</page>
在 Trace 中可以看到创建 Element,设置 Element 的 Event,Class 等属性的完整过程。
Resolve
Resolve 阶段根据 Element 的 class,内链样式等属性解析 Element 的 style,并创建排版节点树。
如下图所示,Resolve 阶段完整执行流程和解析 <view className="Background" />
对应 style 的流程。
排版
排版阶段完成排版节点的测量和排版。如下图所示,<text>Tap the logo and have fun!</text>
节点的排版过程。
Flush
完成布局后将创建平台层 UI 节点,并设置各种属性和布局信息。
TIP
只包含排版属性的 Element 节点不会创建平台层 UI。
嵌套 text 会和父节点 text 合并,创建一个平台层 UI。
hello-world 项目首帧以下节点会创建平台层 UI:
<page>
<view className="Background" />
<view className="Banner">
<view className="Logo" bindtap="{onTap}">
<image src="{lynxLogo}" className="Logo--lynx" />}
</view>
</view>
<text className="Title">React</text>
<text className="Subtitle">on Lynx</text>
<image src="{arrow}" className="Arrow" />
<text className="Description">Tap the logo and have fun!</text>
<text className="Hint"> Edit " src/App.tsx " to see updates!</text>
</page>
绘制
在系统的渲染回调到来时,完成平台层 UI 的首帧绘制。
更新渲染流程
以 hello-world 项目点击 Logo 触发更新为例,介绍组件更新渲染流程。
触发点击事件
点击 Logo 后,LynxView 会处理点击事件并发送点击事件到后台线程,触发对应节点的点击事件回调。
为了在 Trace 中更清晰的看到点击事件回调的执行时机,可以使用 Trace API 添加自定义 Trace 事件。
const onTap = useCallback(() => {
'background only';
lynx.performance.profileStart('onTap');
setAlterLogo(!alterLogo);
lynx.performance.profileEnd();
}, [alterLogo]);
App 组件在点击事件回调中更新了 alterLogo 的状态。
组件 Diff 流程
更新组件状态后,会触发组件的 Diff 流程,image 节点发生变化:
<view className="Logo" bindtap="{onTap}">
+ <image src="{reactLynxLogo}" className="Logo--react" /> - -
<image src="{lynxLogo}" className="Logo--lynx" />
</view>
如下图所示,App 组件的 Diff 流程:
组件更新同步
组件 Diff 完成后会将更新信息同步到 Engine 线程,驱动后续 UI 变更。在 Trace 中点击 CallLepusMethod
事件,可以看到组件 Diff 完成后对应的 UI 更新过程。
UI 更新
Engine 线程展示了 Element 树的更新过程。如下图所示,Element 树移除了一个节点,并新增了一个 image 节点。
Element 更新完成后会重新排版和更新 UI。
触发 useEffect 回调
Element 树更新完成后通知后台线程触发组件的 useEffect 回调。
为了在 Trace 中更清晰的看到 useEffect 回调的执行时机,使用 Trace API 添加自定义 trace。
useEffect(() => {
console.info('Hello, ReactLynx');
}, []);
useEffect(() => {
lynx.performance.profileMark('useEffect', {
args: { alterLogo: `${alterLogo}` },
});
}, [alterLogo]);
