渲染、提交和安装
本文档涉及正在积极推出的 新架构。
¥This document refers to the New Architecture, that is in active roll-out.
React Native 渲染器通过一系列工作将 React 逻辑渲染到 主机平台。此工作序列称为渲染管道,发生在初始渲染和 UI 状态更新时。本文档介绍了渲染管道及其在这些场景中的不同之处。
¥The React Native renderer goes through a sequence of work to render React logic to a host platform. This sequence of work is called the render pipeline and occurs for initial renders and updates to the UI state. This document goes over the render pipeline and how it differs in those scenarios.
渲染管道可以分为三个一般阶段:
¥The render pipeline can be broken into three general phases:
-
使成为:React 执行产品逻辑,在 JavaScript 中创建 React 元素树。渲染器从这棵树中用 C++ 创建一个 React 影子树。
¥Render: React executes product logic which creates a React Element Trees in JavaScript. From this tree, the renderer creates a React Shadow Tree in C++.
-
提交:React Shadow Tree 完全创建后,渲染器会触发提交。这会将 React Element Tree 和新创建的 React Shadow Tree 提升为要安装的“下一棵树”。这也安排了其布局信息的计算。
¥Commit: After a React Shadow Tree is fully created, the renderer triggers a commit. This promotes both the React Element Tree and the newly created React Shadow Tree as the “next tree” to be mounted. This also schedules calculation of its layout information.
-
山:React Shadow Tree 现在带有布局计算的结果,被转换为 主机视图树。
¥Mount: The React Shadow Tree, now with the results of layout calculation, is transformed into a Host View Tree.
渲染管线的各个阶段可能发生在不同的线程上。有关更多详细信息,请参阅 线程模型 文档。
¥The phases of the render pipeline may occur on different threads. Refer to the Threading Model doc for more detail.
初始渲染
¥Initial Render
想象一下你想要渲染以下内容:
¥Imagine you want to render the following:
function MyComponent() {
return (
<View>
<Text>Hello, World</Text>
</View>
);
}
// <MyComponent />
在上面的示例中,<MyComponent />
是 React 元素。React 通过调用它(或者它的 render
方法,如果使用 JavaScript 类实现)来递归地将这个 React Element 减少到终端 React 宿主组件,直到每个 React Element 无法进一步减少。现在你有一个 React 宿主组件 的 React 元素树。
¥In the example above, <MyComponent />
is a React Element. React recursively reduces this React Element to a terminal React Host Component by invoking it (or its render
method if implemented with a JavaScript class) until every React Element cannot be reduced any further. Now you have a React Element Tree of React Host Components.
阶段 1。渲染
¥Phase 1. Render
在元素缩减的过程中,当调用每个 React Element 时,渲染器也会同步创建一个 React 影子节点。这种情况仅发生在 React Host 组件上,而不会发生在 React 复合组件 上。在上面的示例中,<View>
导致创建 ViewShadowNode
对象,<Text>
导致创建 TextShadowNode
对象。值得注意的是,从来不存在直接代表 <MyComponent>
的 React Shadow Node。
¥During this process of element reduction, as each React Element is invoked, the renderer also synchronously creates a React Shadow Node. This happens only for React Host Components, not for React Composite Components. In the example above, the <View>
leads to the creation of a ViewShadowNode
object, and the
<Text>
leads to the creation of a TextShadowNode
object. Notably, there is never a React Shadow Node that directly represents <MyComponent>
.
每当 React 在两个 React Element Node 之间创建父子关系时,渲染器就会在相应的 React Shadow Node 之间创建相同的关系。这就是 React Shadow Tree 的组装方式。
¥Whenever React creates a parent-child relationship between two React Element Nodes, the renderer creates the same relationship between the corresponding React Shadow Nodes. This is how the React Shadow Tree is assembled.
额外细节
¥Additional Details
-
这些操作(创建 React Shadow Node、创建两个 React Shadow Node 之间的父子关系)是同步且线程安全的操作,通常在 JavaScript 线程上从 React (JavaScript) 执行到渲染器 (C++) 中。
¥The operations (creation of React Shadow Node, creation of parent-child relationship between two React Shadow Nodes) are synchronous and thread-safe operations that are executed from React (JavaScript) into the renderer (C++), usually on the JavaScript thread.
-
React 元素树(及其组成的 React 元素节点)不会无限期存在。它是 React 中通过“纤维”实现的时间表示。每个代表主机组件的“纤程”都存储一个指向 React Shadow Node 的 C++ 指针,这由 JSI 实现。在本文档中了解有关“纤维”的更多信息。
¥The React Element Tree (and its constituent React Element Nodes) do not exist indefinitely. It is a temporal representation materialized by “fibers” in React. Each “fiber” that represents a host component stores a C++ pointer to the React Shadow Node, made possible by JSI. Learn more about “fibers” in this document.
-
React Shadow Tree 是不可变的。为了更新任何 React Shadow Node,渲染器会创建一个新的 React Shadow Tree。但是,渲染器提供克隆操作以使状态更新性能更高(有关更多详细信息,请参阅 React 状态更新)。
¥The React Shadow Tree is immutable. In order to update any React Shadow Node, the renderer creates a new React Shadow Tree. However, the renderer provides cloning operations to make state updates more performant (see React State Updates for more details).
在上面的示例中,渲染阶段的结果如下所示:
¥In the example above, the result of the render phase looks like this:
React Shadow Tree 完成后,渲染器会触发 React Element Tree 的提交。
¥After the React Shadow Tree is complete, the renderer triggers a commit of the React Element Tree.
阶段 2。提交
¥Phase 2. Commit
提交阶段由两个操作组成:布局计算和树木升级。
¥The commit phase consists of two operations: Layout Calculation and Tree Promotion.
-
布局计算:该操作计算每个 React Shadow Node 的位置和大小。在 React Native 中,这涉及调用 Yoga 来计算每个 React Shadow Node 的布局。实际计算需要每个 React Shadow Node 的样式,这些样式源自 JavaScript 中的 React Element。它还需要 React Shadow Tree 根的布局约束,这决定了结果节点可以占用的可用空间量。
¥Layout Calculation: This operation calculates the position and size of each React Shadow Node. In React Native, this involves invoking Yoga to calculate the layout of each React Shadow Node. The actual calculation requires each React Shadow Node’s styles which originate from a React Element in JavaScript. It also requires the layout constraints of the root of the React Shadow Tree, which determines the amount of available space that the resulting nodes can occupy.
-
树升级(新树→下一棵树):此操作将新的 React Shadow Tree 提升为要安装的“下一棵树”。此提升表明新的 React Shadow Tree 拥有要挂载的所有信息,并代表了 React Element Tree 的最新状态。“下一棵树”安装在 UI 线程的下一个“tick”上。
¥Tree Promotion (New Tree → Next Tree): This operation promotes the new React Shadow Tree as the “next tree” to be mounted. This promotion indicates that the new React Shadow Tree has all the information to be mounted and represents the latest state of the React Element Tree. The “next tree” mounts on the next “tick” of the UI Thread.
额外细节
¥Additional Details
-
这些操作在后台线程上异步执行。
¥These operations are asynchronously executed on a background thread.
-
大多数布局计算完全在 C++ 内执行。然而,某些组件的布局计算取决于主机平台(例如
Text
、TextInput
等)。文本的大小和位置特定于每个主机平台,并且需要在主机平台层上计算。为此,Yoga 调用主机平台中定义的函数来计算组件的布局。¥Majority of layout calculation executes entirely within C++. However, the layout calculation of some components depend on the host platform (e.g.
Text
,TextInput
, etc.). Size and position of text is specific to each host platform and needs to be calculated on the host platform layer. For this purpose, Yoga invokes a function defined in the host platform to calculate the component’s layout.
阶段 3。挂载
¥Phase 3. Mount
安装阶段将 React Shadow Tree(现在包含来自布局计算的数据)转换为在屏幕上具有渲染像素的 Host View Tree。提醒一下,React 元素树如下所示:
¥The mount phase transforms the React Shadow Tree (which now contains data from layout calculation) into a Host View Tree with rendered pixels on the screen. As a reminder, the React Element Tree looks like this:
<View>
<Text>Hello, World</Text>
</View>
在较高层次上,React Native 渲染器为每个 React Shadow Node 创建一个相应的 主机视图 并将其安装在屏幕上。在上面的示例中,渲染器为 <View>
创建 android.view.ViewGroup
的实例,为 <Text>
创建 android.widget.TextView
的实例,并用“Hello World”填充它。同样,对于 iOS,会创建一个 UIView
,并使用对 NSLayoutManager
的调用填充文本。然后,每个主机视图都被配置为使用其 React Shadow Node 中的 props,并使用计算出的布局信息来配置其大小和位置。
¥At a high level, React Native renderer creates a corresponding Host View for each React Shadow Node and mounts it on screen. In the example above, the renderer creates an instance of android.view.ViewGroup
for the <View>
and android.widget.TextView
for <Text>
and populates it with “Hello World”. Similarly for iOS a UIView
is created and text is populated with a call to NSLayoutManager
. Each host view is then configured to use props from its React Shadow Node, and its size and position is configured using the calculated layout information.
更详细地说,安装阶段包括以下三个步骤:
¥In more detail, the mounting phase consists of these three steps:
-
树差异:此步骤完全用 C++ 计算“先前渲染的树”和“下一棵树”之间的差异。结果是要在主机视图上执行的原子突变操作的列表(例如
createView
、updateView
、removeView
、deleteView
等)。这一步也是 React Shadow Tree 被展平的地方,以避免创建不必要的宿主视图。有关该算法的详细信息,请参阅 视图扁平化。¥Tree Diffing: This step computes the diff between the “previously rendered tree” and the “next tree” entirely in C++. The result is a list of atomic mutation operations to be performed on host views (e.g.
createView
,updateView
,removeView
,deleteView
, etc). This step is also where the React Shadow Tree is flattened to avoid creating unnecessary host views. See View Flattening for details about this algorithm. -
树升级(下一棵树→渲染树):此步骤以原子方式将“下一棵树”提升为“先前渲染的树”,以便下一个安装阶段计算与正确树的差异。
¥Tree Promotion (Next Tree → Rendered Tree): This step atomically promotes the “next tree” to “previously rendered tree” so that the next mount phase computes a diff against the proper tree.
-
查看安装:此步骤将原子突变操作应用到相应的宿主视图上。此步骤在主机平台的 UI 线程上执行。
¥View Mounting: This step applies the atomic mutation operations onto corresponding host views. This step executes in the host platform on UI thread.
额外细节
¥Additional Details
-
这些操作在 UI 线程上同步执行。如果提交阶段在后台线程上执行,则安装阶段将安排在 UI 线程的下一个“tick”中。另一方面,如果提交阶段在 UI 线程上执行,则安装阶段在同一线程上同步执行。
¥The operations are synchronously executed on UI thread. If the commit phase executes on background thread, the mounting phase is scheduled for the next “tick” of UI thread. On the other hand, if the commit phase executes on UI thread, mounting phase executes synchronously on the same thread.
-
安装阶段的调度、实现和执行在很大程度上取决于主机平台。例如,目前 Android 和 iOS 之间的安装层渲染器架构有所不同。
¥Scheduling, implementation, and execution of the mounting phase heavily depends on the host platform. For example, the renderer architecture of the mounting layer currently differs between Android and iOS.
-
在初始渲染期间,“先前渲染的树”是空的。因此,树差异步骤将产生一个突变操作列表,其中仅包含创建视图、设置 props 以及将视图相互添加。处理 React 状态更新 时,树比较对于性能变得更加重要。
¥During the initial render, the “previously rendered tree” is empty. As such, the tree diffing step will result in a list of mutation operations that consists only of creating views, setting props, and adding views to each other. Tree diffing becomes more important for performance when processing React State Updates.
-
在当前的生产测试中,React Shadow Tree 通常由大约 600-1000 个 React Shadow Nodes(视图扁平化之前)组成,视图扁平化之后树会减少到约 200 个节点。在 iPad 或桌面应用上,这个数量可能会增加 10 倍。
¥In current production tests, a React Shadow Tree typically consists of about 600-1000 React Shadow Nodes (before view flattening), the trees get reduced to ~200 nodes after view flattening. On iPad or desktop apps, this quantity may increase 10-fold.
React 状态更新
¥React State Updates
让我们探索一下 React 元素树状态更新时渲染管道的每个阶段。假设你在初始渲染中渲染了以下组件:
¥Let’s explore each phase of the render pipeline when the state of a React Element Tree is updated. Let’s say, you’ve rendered the following component in an initial render:
function MyComponent() {
return (
<View>
<View
style={{backgroundColor: 'red', height: 20, width: 20}}
/>
<View
style={{backgroundColor: 'blue', height: 20, width: 20}}
/>
</View>
);
}
应用 初始渲染 部分中描述的内容,你将期望创建以下树:
¥Applying what was described in the Initial Render section, you would expect the following trees to be created:
请注意,节点 3 映射到具有红色背景的主机视图,节点 4 映射到具有蓝色背景的主机视图。假设由于 JavaScript 产品逻辑中的状态更新,第一个嵌套的 <View>
的背景从 'red'
变为 'yellow'
。新的 React 元素树可能如下所示:
¥Notice that Node 3 maps to a host view with a red background, and Node 4 maps to a host view with a blue background. Assume that as the result of a state update in JavaScript product logic, the background of the first nested <View>
changes from 'red'
to 'yellow'
. This is what the new React Element Tree might look:
<View>
<View
style={{backgroundColor: 'yellow', height: 20, width: 20}}
/>
<View
style={{backgroundColor: 'blue', height: 20, width: 20}}
/>
</View>
React Native 是如何处理这个更新的?
¥How is this update processed by React Native?
当状态更新发生时,渲染器需要从概念上更新 React 元素树,以便更新已安装的宿主视图。但为了保证线程安全,React Element Tree 和 React Shadow Tree 都必须是不可变的。这意味着 React 必须为每棵树创建一个新的副本,其中包含新的 props、样式和子项,而不是改变当前的 React Element Tree 和 React Shadow Tree。
¥When a state update occurs, the renderer needs to conceptually update the React Element Tree in order to update the host views that are already mounted. But in order to preserve thread safety, both the React Element Tree as well as the React Shadow Tree must be immutable. This means that instead of mutating the current React Element Tree and React Shadow Tree, React must create a new copy of each tree which incorporates the new props, styles, and children.
让我们探索状态更新期间渲染管道的每个阶段。
¥Let’s explore each phase of the render pipeline during a state update.
阶段 1。渲染
¥Phase 1. Render
当 React 创建包含新状态的新 React 元素树时,它必须克隆受更改影响的每个 React 元素和 React Shadow 节点。克隆后,将提交新的 React Shadow Tree。
¥When React creates a new React Element Tree that incorporates the new state, it must clone every React Element and React Shadow Node that is impacted by the change. After cloning, the new React Shadow Tree is committed.
React Native 渲染器利用结构共享来最小化不可变性的开销。当克隆 React 元素以包含新状态时,将克隆直到根的路径上的每个 React 元素。仅当 React 元素需要更新其 props、样式或子元素时,React 才会克隆它。任何未因状态更新而改变的 React 元素都由新旧树共享。
¥React Native renderer leverages structural sharing to minimize the overhead of immutability. When a React Element is cloned to include the new state, every React Element that is on the path up to the root is cloned. React will only clone a React Element if it requires an update to its props, style, or children. Any React Elements that are unchanged by the state update are shared by the old and new trees.
在上面的示例中,React 使用以下操作创建新树:
¥In the above example, React creates the new tree using these operations:
-
克隆节点(节点 3,
{backgroundColor: 'yellow'}
) → 节点 3'¥CloneNode(Node 3,
{backgroundColor: 'yellow'}
) → Node 3' -
克隆节点(节点 2) → 节点 2'
¥CloneNode(Node 2) → Node 2'
-
AppendChild(Node 2', 节点 3')
¥AppendChild(Node 2', Node 3')
-
AppendChild(Node 2', Node 4)
-
克隆节点(节点 1) → 节点 1'
¥CloneNode(Node 1) → Node 1'
-
AppendChild(Node 1', 节点 2')
¥AppendChild(Node 1', Node 2')
经过这些操作后,节点 1' 代表新的 React 元素树的根。让我们将 T 分配给“先前渲染的树”,将 T' 分配给“新树”:
¥After these operations, Node 1' represents the root of the new React Element Tree. Let's assign T to the “previously rendered tree” and T' to the “new tree”:
请注意 T 和 T' 如何共享节点 4。结构共享可提高性能并减少内存使用。
¥Notice how T and T' both share Node 4. Structural sharing improves performance and reduces memory usage.
阶段 2。提交
¥Phase 2. Commit
React 创建新的 React Element Tree 和 React Shadow Tree 后,必须提交它们。
¥After React creates the new React Element Tree and React Shadow Tree, it must commit them.
-
布局计算:与 初始渲染 期间的布局计算类似。一个重要的区别是布局计算可能会导致共享的 React Shadow Node 被克隆。发生这种情况的原因是,如果共享 React Shadow Node 的父节点发生布局更改,则共享 React Shadow Node 的布局也可能会更改。
¥Layout Calculation: Similar to Layout Calculation during Initial Render. One important difference is that layout calculation may cause shared React Shadow Nodes to be cloned. This can happen because if the parent of a shared React Shadow Node incurs a layout change, the layout of the shared React Shadow Node may also change.
-
树升级(新树→下一棵树):与 初始渲染 期间的树木推广类似。
¥Tree Promotion (New Tree → Next Tree): Similar to Tree Promotion during Initial Render.
阶段 3。挂载
¥Phase 3. Mount
-
树升级(下一棵树→渲染树):此步骤以原子方式将“下一棵树”提升为“先前渲染的树”,以便下一个安装阶段计算与正确树的差异。
¥Tree Promotion (Next Tree → Rendered Tree): This step atomically promotes the “next tree” to “previously rendered tree” so that the next mount phase computes a diff against the proper tree.
-
树差异:此步骤计算“先前渲染的树”(T) 和“下一棵树”(T') 之间的差异。结果是要在主机视图上执行的原子突变操作的列表。
¥Tree Diffing: This step computes the diff between the “previously rendered tree” (T) and the “next tree” (T'). The result is a list of atomic mutation operations to be performed on host views.
-
在上面的例子中,操作包括:
UpdateView(**Node 3**, {backgroundColor: 'yellow'})
¥In the above example, the operations consist of:
UpdateView(**Node 3**, {backgroundColor: 'yellow'})
-
可以计算任何当前安装的树与任何新树的差异。渲染器可以跳过树的某些中间版本。
¥The diff can be calculated for any currently mounted tree with any new tree. The renderer can skip some intermediate versions of the tree.
-
-
查看安装:此步骤将原子突变操作应用到相应的宿主视图上。在上面的示例中,只有视图 3 的
backgroundColor
会被更新(为黄色)。¥View Mounting: This step applies the atomic mutation operations onto corresponding host views. In the above example, only the
backgroundColor
of View 3 will be updated (to yellow).
React Native 渲染器状态更新
¥React Native Renderer State Updates
对于 Shadow Tree 中的大多数信息,React 是唯一所有者和唯一事实来源。所有数据均源自 React,并且数据是单向流动的。
¥For most information in the Shadow Tree, React is the single owner and single source of truth. All data originates from React and there is a single-direction flow of data.
然而,有一个例外但重要的机制:C++ 中的组件可以包含不直接暴露给 JavaScript 的状态,而 JavaScript 并不是事实的来源。C++ 和主机平台控制此 C++ 状态。一般来说,只有当你正在开发需要 C++ 状态的复杂主机组件时,这才有意义。绝大多数主机组件不需要此功能。
¥However, there is one exception and important mechanism: components in C++ can contain state that is not directly exposed to JavaScript, and JavaScript is not the source of truth. C++ and Host Platform control this C++ State. Generally, this is only relevant if you are developing a complicated Host Component that needs C++ State. The vast majority of Host Components do not need this functionality.
例如,ScrollView
使用此机制让渲染器知道当前偏移量是多少。更新是从主机平台触发的,特别是从代表 ScrollView
组件的主机视图触发的。有关偏移量的信息在 measure 这样的 API 中使用。由于此更新源于主机平台,并且不影响 React Element Tree,因此此状态数据由 C++ State 保存。
¥For example, ScrollView
uses this mechanism to let the renderer know what the current offset is. The update is triggered from the host platform, specifically from the host view that represents the ScrollView
component. The information about offset is used in an API like measure. Since this update stems from the host platform, and does not affect the React Element Tree, this state data is held by C++ State.
从概念上讲,C++ 状态更新与上述 React 状态更新 类似。有两个重要的区别:
¥Conceptually, C++ State updates are similar to the React State Updates described above. With two important differences:
-
由于不涉及 React,因此他们跳过了“渲染阶段”。
¥They skip the “render phase” since React is not involved.
-
更新可以在任何线程上发起和发生,包括主线程。
¥The updates can originate and happen on any thread, including the main thread.
阶段 2。提交
¥Phase 2. Commit
执行 C++ 状态更新时,代码块请求更新 ShadowNode
(N) 以将 C++ 状态设置为值 S。React Native 渲染器将反复尝试获取 N 的最新提交版本,使用新状态 S 克隆它,并将 N' 提交到树中。如果 React 或另一个 C++ 状态更新在此期间执行了另一次提交,则 C++ 状态提交将失败,并且渲染器将多次重试 C++ 状态更新,直到提交成功。这可以防止真相来源冲突和竞争。
¥When performing a C++ State update, a block of code requests an update of a ShadowNode
(N) to set C++ State to value S. React Native renderer will repeatedly attempt to get the latest committed version of N, clone it with a new state S, and commit N’ to the tree. If React, or another C++ State update, has performed another commit during this time, the C++ State commit will fail and the renderer will retry the C++ State update many times until a commit succeeds. This prevents source-of-truth collisions and races.
阶段 3。挂载
¥Phase 3. Mount
安装阶段实际上与 React 状态更新的挂载阶段 相同。渲染器仍然需要重新计算布局、执行树差异等。有关详细信息,请参阅上面的部分。
¥The Mount Phase is practically identical to the Mount Phase of React State Updates. The renderer still needs to recompute layout, perform a tree diff, etc. See the sections above for details.