Skip to content

渲染、提交和挂载

本文档涉及正在积极推出的新渲染器 Fabric 的架构。

React Native 渲染器通过一系列工作将 React 逻辑渲染到主机平台。 这一系列工作称为渲染管道,用于初始渲染和更新 UI 状态。 本文档介绍了渲染管道以及它在这些场景中的不同之处。

渲染管道可以分为三个一般阶段:

渲染管道的阶段可能发生在不同的线程上。 有关详细信息,请参阅线程模型文档。

image-20220715164019028

初始渲染#

假设您要渲染以下内容:

function MyComponent() {
return (
<View>
<Text>Hello, World</Text>
</View>
)
}
// <MyComponent />

在上面的例子中,<MyComponent /> 是一个 React 元素。 React 通过调用它(如果是使用 JavaScript 类实现则调用它的 render 方法)递归地将这个 React 元素缩减为终端 React 主机组件,直到每个 React 元素都不能被进一步缩减。现在你有一个 React 主机组件的 React 元素树。

阶段一:渲染#

image-20220715164039531

在这个元素缩减的过程中,随着每个 React Element 的调用,渲染器也会同步创建一个 React Shadow Node。这只发生在 React 主机组件,而不是 React Composite 组件。在上面的示例中,<View> 导致创建 ViewShadowNode 对象,<Text> 导致创建 TextShadowNode 对象。 值得注意的是,从来没有直接代表 <MyComponent> 的 React Shadow Node。

每当 React 在两个 React 元素节点之间创建父子关系时,渲染器都会在相应的 React 影子节点之间创建相同的关系。 这就是 React Shadow Tree 的组装方式。

额外细节

在上面的示例中,渲染阶段的结果如下所示:

image-20220715164056410

React Shadow Tree 完成后,渲染器会触发 React Element Tree 的提交。

阶段二:提交#

image-20220715164112806

提交阶段包括两个操作:布局计算和 Tree 提升。

image-20220715164128686

备注:Litho 和 Yoga 都是基于 Flexbox,Flexbox 是一种布局模式,标准来自于前端 CSS,可以理解为类似 Linear Layout 提供的一种布局规范。其中 Litho 底层依赖于 Yoga 部分模块,但是两者在原理上有一些区别。

额外细节

阶段三:挂载#

image-20220715164145639

挂载阶段将 React Shadow Tree(现在包含来自布局计算的数据)转换为在屏幕上呈现像素的主机视图树。提醒一下,React 元素树如下所示:

<View>
<Text>Hello, World</Text>
</View>

在高层次上,React Native 渲染器为每个 React Shadow Node 创建一个对应的 Host View 并将其安装在屏幕上。

在上面的示例中,渲染器为 <View> 创建了一个 android.view.ViewGroup 实例,为 <Text> 创建了一个 android.widget.TextView 实例,并用“Hello World”填充它。

同样,对于 iOS,创建 UIView 并使用对 NSLayoutManager 的调用填充文本。

每个 Host View 都将会使用使用来自其 React Shadow 节点的 props 来计算出的布局信息配置其大小和位置。

image-20220715164200535

更详细地说,安装阶段包括以下三个步骤:

额外细节

React 状态更新#

当 React 元素树的状态更新时,让我们探索渲染管道的每个阶段。

假设您在初始渲染中渲染了以下组件:

function MyComponent() {
return (
<View>
<View style={{ backgroundColor: 'red', height: 20, width: 20 }} />
<View style={{ backgroundColor: 'blue', height: 20, width: 20 }} />
</View>
)
}

根据初始渲染部分中描述的内容,我们知道会创建以下树:

image-20220715164221460

请注意,节点 3 所映射的具有红色背景的主机视图,以及节点 4 映射到的具有蓝色背景的主机视图。假设由于 JavaScript 产品逻辑中的状态更新,第一个嵌套 <View> 的背景从“红色”变为“黄色”。这是新的 React 元素树的外观:

<View>
<View style={{ backgroundColor: 'yellow', height: 20, width: 20 }} />
<View style={{ backgroundColor: 'blue', height: 20, width: 20 }} />
</View>

React Native 是如何处理这个更新的?

当状态更新发生时,渲染器首先需要更新 React 元素树,从而以便更新已经挂载的 Host 视图。但是为了保持线程安全,React Element Tree 和 React Shadow Tree 都必须是不可变的。这意味着,不是改变当前的 React Element Tree 和 React Shadow Tree,React 必须为每棵树创建一个新的副本,其中包含新的 props、styles 和 children。

让我们在状态更新期间探索渲染管道的每个阶段。

阶段一:渲染#

image-20220715164255498

当 React 创建一个包含新状态的新 React 元素树时,它必须克隆每个受更改影响的 React 元素和 React Shadow 节点。 克隆后,新的 React Shadow Tree 被提交。

React Native 渲染器利用结构共享来最小化开销。当一个 React 元素被克隆时,副本会包含新的状态,并且该元素到根结点上的所有 React 元素都会被克隆。**React 只有在需要更新其 props、style 或 children 时才会克隆 React Element。**状态更新未更改的任何 React 元素都由新旧树共享。

在上面的示例中,React 使用以下操作创建新树:

在这些操作之后,节点 1’ 代表新的 React 元素树的根。我们使用 T 代表“上一次的渲染树”,使用 T’ 表示“新树”:

image-20220715164311388

注意 T 和 T’ 将会共享节点 4。结构共享提高了性能并减少了内存使用。

阶段二:提交#

image-20220715164326525

在 React 创建新的 React Element Tree 和 React Shadow Tree 之后,将会提交它们。

阶段三:挂载#

image-20220715164341742

image-20220715164358434

React Native 渲染器状态更新#

对于 Shadow Tree 中的大多数信息,React 是唯一的所有者和唯一的事实来源。 所有数据都来自 React,并且存在单向数据流。

但是,有一个重要的机制是一个例外,那就是 C++ 中的组件可以包含不直接暴露给 JavaScript 的状态,此时 JavaScript 就不是事实的来源。C++ 和平台来控制此 C++ 状态。通常,这仅在您开发需要 C++ 状态的复杂主机组件时才相关。绝大多数主机组件不需要此功能。

例如,ScrollView 使用这种机制让渲染器知道当前偏移量是多少。因为更新是从宿主平台所触发的,特别是从代表 ScrollView 组件的宿主视图。在测量相关的 API 中会使用有关偏移量的信息。由于此更新源于主机平台,并且不影响 React Element Tree,因此此状态数据由 C++ State 保存。

从概念上讲,C++ 状态更新类似于上面描述的 React 状态更新。但是有两个重要区别:

  1. 他们跳过“渲染阶段”,因为不涉及 React。

  2. 更新可以在任何线程上发起和发生,包括主线程。

阶段二:提交#

image-20220715164415905

在执行 C++ 状态更新时,代码块请求更新 ShadowNode (N) 以将 C++ 状态设置为值 S。React Native 渲染器将反复尝试获取 N 的最新提交版本,将其克隆为新状态 S,并将 N’ 提交到树。如果 React 或另一个 C++ 状态更新在此期间执行了另一个提交,则 C++ 状态提交将失败,渲染器将多次重试 C++ 状态更新,直到提交成功。 这可以防止真相来源的冲突和竞争。

阶段三:挂载#

image-20220715164434472

挂载阶段实际上与 React 状态更新的挂载阶段相同。渲染器仍然需要重新计算布局执行树差异等。有关详细信息,请参阅上面的部分。