React Native 渲染原理
React Native 渲染原理
由于 React Native Fabric 还在开发中,我们还是来聊聊当前 React Native 的渲染原理
水平方向以不同线程的角度来看渲染的过程
无论是还在开发中的新架构,还是当前的旧架构,一个 React Native App 渲染过程都会涉及到三个线程
- Main thread
- JavaScript thread
- Shadow thread
先来介绍下每个线程的作用
Main Thread:
主线程又称 UI thread,主要负责 UI 的渲染以及用户行为的监听等等,是 App 启动时首先创建的线程
JavaScript Thread:
通过 JavaScript Core 或者是 Hermes 引擎,JavaScript 代码的解析和执行是由 JS 线程负责的。和浏览器环境不同的是, JS 代码解析和执行在独立 JS 线程,而不是和 UI 渲染共用一个线程。
Shadow Thread:
前两个线程都比较好理解,那 Shadow 线程是做什么的呢?要回答这个问题,首先我们需要理解 React 的原理。
我们都知道在浏览器环境 React DOM 会维护一个 Virtual DOM,Virtual DOM 实际上是在内存中的一个树形结构(Tree-Like)的 JS Object,用来映射真实的 DOM Node Tree。每次 State 更新后,React 会生成一个新的 Virtual DOM,通过和旧的 virtual DOM 做对比(Diffing),从而仅仅将需要变更的部分同步到真实 DOM(Reconciliation),从而减少 Dom Tree 的变更,从而减少浏览器的绘制工作。
与之类似的是,在 React Native,React 也会维护一个类似的 "Virtual DOM",也会有 Diffing 的过程。但是不同于浏览器环境中 React DOM 直接调用浏览器 API 来完成真正的 DOM 更新操作,Native 环境下 React Native 是通过 Bridge ,将需要变更的指令(Commands)以字符串的方式发送到 Native Side,而对应在 Native 这边负责处理这些指令的进程就是 Shadow Thread。
Shadow Thread 通过维护一个 Shadow Tree 来计算 "Virtual DOM" 在 Native 页面的实际布局,然后通过 Bridge 异步通知 Main Thread 渲染 UI。
Shadow Tree 可以理解为是 "Virtual DOM" 在 Native 的映射,拥有和 Virtual DOM 相同的树形层级关系
首次渲染流程
- Native 打开 RN 页面
- JS 线程运行,Virtual DOM Tree 被创建
- JS 线程异步通知 Shadow Thread 有节点变更
- Shadow Thread 创建 Shadow Tree
- Shadow Thread 计算布局,异步通知 Main Thread 创建 Views
- Main Thread 处理 View 的创建,展示给用户
更新触发重新渲染流程
- Virtual DOM Tree 发生变更(比如 background-color 发生变化)
- JS 线程异步通知 Shadow Thread 有节点变更
- Shadow Tread 更新 Shadow Tree,计算新布局,同时异步通知 Main Thread 更新 View
- Main Thread 更新 UI
线程间通讯 - Bridge
上面我们提到的线程间的异步通讯,都是通过 Bridge 来实现的。Bridge 由 C++ 实现,可以复用在 iOS & Android,用与 JS side 和 Native side 相互通讯 (two-way-communacation)
这种方式的优点是:
- Main Thread 不会阻塞 (block),也就是说,UI 渲染是流畅的,所以用户体验不会卡顿
但是只有异步调用会导致一些问题无法解决,
- 比如一些对同步需求强烈的场景:
- 手势处理 (Gesture handling),异步会导致卡顿,延迟
- 长列表快速滚动 (Fast scroll on long list) 出现白屏的现象
- Input 输入框的的 callback 延迟导致输入闪烁
- Native 页面内嵌 RN 组件的闪屏 (Shank) 问题(即Native 页面已经渲染完成,但是 RN 组件还处于初始高度为 0 的状态,等 RN 组件完成渲染,突然会高度变化)
2. 另外一个问题是,由于 JS 侧无法和 Native 侧同步内存对象,一些数据量大的信息(比如图片)只能通过 Bridge 传输,会导致通讯的成本很高。
垂直方向从调用链路来看渲染过程
按照调用的顺序,依次是
- React Component,也就是 JS 代码 render 的 React 组件
- React Native Renderer 会将 React Component 转换为 "Virtual DOM tree"
- React Native Bridge 会将 "Virtual DOM tree" 更新的 commands 传递到 Native Side
- UI Manager Module 是 Native Side 处理 commands 的模块,负责根据 commands 创建/更新(与 "Virtual DOM tree" 对应的 Native 这边的) Shadow Tree
- Shadow Tree / Layout,一旦 Shadow Tree 完成更新,就会触发每个 Shadow Tree Node 的布局信息
- View Managers,一旦有了每个节点的布局信息,便会通知对应 View Manager 渲染 UI
整体架构
最后补充一个完整的 React Native 的完整架构图
Ref
- https://www.youtube.com/watch?v=UcqRXTriUVI&ab_channel=ReactConf
- https://www.youtube.com/watch?v=ZwX3i3e1Vfs&ab_channel=WixEngineeringTechTalks
- https://www.youtube.com/watch?v=FqMTXagEvHo&ab_channel=CallstackEngineers
- https://www.youtube.com/watch?v=i793Qm6kv3U&ab_channel=CrossComm%2CInc
- https://www.youtube.com/watch?v=8va9prUqjnA&ab_channel=CallstackEngineers
- https://www.youtube.com/watch?v=7gm0owyO8HU&ab_channel=ReactConferencesbyGitNation