作者:Chen Feldman
发布日期:2019.07.30
原文链接:https://medium.com/swlh/react-native-a-bridge-to-project-fabric-part-1-5af6a53c0d83
翻译中有部分是意译。
最近几年,React Native已经成为最流行的移动端App开发框架之一。解决了很多开发者经常思考的问题:应该使用原生开发客户端app,还是使用web开发Hybird app。
使用RN开发需要web方面的知识和React经验,因为它基本是基于React框架的。需要比直接使用Cordova这样库更难一些,但是比写Swift和Kotlin 这两个原生客户端的代码要轻松很多。RN可以通过写纯js代码获得和原生类似的性能体验。
在开始之前,我们先介绍一下,相比于其他方案,React Native的定位是什么。
在上图中,选取了一些常见的App开发方式。从左到右,可以分成3个阵营:
原生Native。这意味着你想去使用原生语言开发App,比如ios的Swift或Android的Kotlin。肯定会获得最好的性能,并且可以充分利用设备硬件和原生API。但是,这需要学习两种不同的编程语言,维护两套代码,并且可能有双倍的bug,甚至需要有两个不同的开发团队。
Hybrid。如果你是web开发者或者拥有一个已经熟悉JS、HTML、CSS和某些前端库的Web团队,你可以选择这种方案,并使用Cordova/Ionic 通过一些步骤使你的网页变成移动App。这样我们只需要学习一个技术栈,但是,在性能,硬件和API的使用上会有限制。
类原生(其实也算是一种Hybrid)。React Native就属于这类,理论上开发者只需要懂得Web开发知识就可以了。但是学习曲线会比Hybrid要高一些。开发者需要学习怎么使用React Native的库。在某些场景下,可能还需要使用XCode或Android Studio打开项目。但是写的代码可以适用于iOS和Android两个平台,开发上的限制也会比Hybrid要小一些。性能表现会更像原生开发,而且可以更容易地使用一些原生的API。
现在我们理解了RN的定位,让我们对比一下RN和Hybrid的渲染情况:
img从上图可以看出,使用Cordova的App是放在webView里的。这更像是一个在App里的浏览器。用web的概念说,这更像一个web app里的iFrame。
左边展示了React Native是怎么组成的,在RN中的每个组件,比如:Text、Button、Image等都有一个相对应的原生组件。所以和很多新接触RN开发者想的不一样,RN并没有编译成原生代码,它做了个JS组件和原生组件的映射。
当我们这样在Render函数里写了个RN组件:
img背后部分预先写好的原生组件会是这样的:
img如果我们用iOS来举例,这些是RN里面的原生组件代码:
img总的来说,RN团队已经为我们创建和映射了所有的原生组件。我们需要去做的只是去基于RN组件和库写JavaScript代码。其他的部分对一般开发者来说都是不可见的,像黑魔法一样。
所以,看上去我们只用写js代码就可以了。让我们一步步来解释一下这个架构是怎样的。从Bridge开始是最合适的:
RN可以分成JS 和 Native两部分,这两部分都有自己的线程
线程通信是通过Bridge通信,传输的是JSON信息,其中包含了module id,method id和一些需要的数据。这两边并不能直接地相互感知,也不能共享相同的内存。
img这有点像不同服务器之间通信,如果你有用不同语言写的后台服务,你将怎么在他们之间通信呢?
很多人认为队列是一个很好的解决方案。你发送JSON/XML队列消息,这个消息遵从相应的协议,并且每个服务都知道怎么去读取和解析成对应的数据和行为。这个队列就很像RN里面的Bridge。
img这里有个例子展示通信是怎样进行的,信息被发送到Bridge。从创建新的View和样式并在屏幕上显示,到在移动端设置子组件和进行一些操作:
img如果想要在console里看到Bridge的消息,只需要把下面这个代码片段放到index. < platform > .js里就可以了
img现在我们知道了RN是怎么通信的,并且开始揭晓其中的“黑魔法”。
js代码和objective-c之间的通信。RN在ios上使用的是内置的JSCore,在Android需要格外编译一个Js引擎
JS引擎知道如何去更加高效地把JS转换成机器语言。
这也是为什么Android App需要更多的时间去加载,因为需要加载JSCore到项目里。不过在0.60.2之后,RN可以使用Hermes,这对于RN来说是更理想的JS引擎。
在这部分,我们将通过解析从点击App图标到App打开这个流程和其中的一些相关细节。
img为了理解RN是怎么在背后创建View的,我们先需要解释一些基础概念:
UIManager:在Native侧,是在iOS/Android里主要运行的线程。只有它有权限可以修改客户端UI。
JS Thread:运行打包好的main.bundle.js文件,这个文件包含了RN的所有业务逻辑、行为和组件。
Shadow Node/Tree:在Native层的一个组件树,可以帮助监听App内的UI变化,有点像ReactJS里的虚拟Dom和Dom之间的关系。
Yoga:用来计算layout。是Facebook写的一个C引擎,用来把基于Flexbox的布局转换到Native的布局系统。
理解了上面的一些基础概念,让我们来看下打开App时,每一步发生了什么:
用户点击App的图标
UIManager线程:加载所有的Native库和Native组件比如 Text、Button、Image等
告诉JS线程,Native部分准备好了,JS侧开始加载main.bundle.js,这里面包含了所有的js和react逻辑以及组件。
JS侧通过Bridge发送一条JSON消息到Native侧,告诉Native怎么创建UI。值得一提的是:所有经过Bridge的通信都是异步的,并且是打包发送的。这是为了避免阻塞UI,举个栗子:
Shadow线程最先拿到消息,然后创建UI树
然后,它使用Yoga布局引擎去获取所有基于flex样式的布局,并且转化成Native的布局,宽、高、间距等。。
之后UIManager执行一些操作并且像这样在屏幕上展示UI:
这些就是启动App是的主要步骤。
RN基于这个架构有以下优点:
UI不会被阻塞:用户感觉到更加流畅
不需要写Native侧的代码:使用RN库的话,很多代码可以只写JavaScript的
性能更加接近Native
整个流程是完整的。开发者不用去控制并且完全了解它
但是,有优点肯定也会有缺点。下面我们介绍一下能够解决现有缺点的新架构:Fabric。
我们已经讨论了RN的当前架构,是时候说一下其中的缺陷。可以看一下Facebook的React团队负责人在她博客里提到的。
当前架构的缺点是:
有两个不同的领域:JS和Native,他们彼此之间并不能真正互相感知,并且也不能共享相同的内存。
它们之间的通信是基于Bridge的异步通信。但是这也意味着,并不能保证数据100%并及时地到达另一侧。
传输大数据非常慢,因为内存不能共享,所有在js和native之间传输的数据都是一次新的复制。
无法同步更新UI。比方说有个FlatList,当我滑动的时候会加载大量的数据。在某些边界场景,当有用户界面交互,但数据还没有返回,屏幕可能会发生闪烁。
RN代码仓库太大了。导致库更重,开源社区贡献代码或发布修复也更慢。
不过不要误会,Facebook自己也正在使用React Native开发各种app,服务百万级的日活用户。同时也有其他有名的公司在生产环境中使用基于当前架构的RN,比如Wix、Bloomberg、Tesla、Zynga等等。
RN开发团队正着手去解决上面提到的缺点。
这是之前的RN架构:
当前结构的主要部分这是新的架构图:
img让我们来解释一下这些新的概念:JSI,Fabric,Turbo Modules,和 CodeGen。
JSI(将会替换Bridge) -- 为了让JS和Native能够互相感知。将不再需要通过Bridge传输序列化JSON。将允许Native对象被导出成Js对象,反过来也可以。两侧也会导出可以被同步调用的API。实际上,架构的其他部分都是基于这个之上的(Fabric,Turbo Modules等,这些下面会解释)
Fabric -- UIManager的新名称,将负责Native端渲染。和当前的Bridge不同的是,它可以通过JSI导出自己的Native函数,在JS层可以直接使用这些函数引用,反过来,Native层也可以直接调用JS层。这带来更好更高效的性能和数据传输。
Turbo Modules。记得上面的Native组件吗?Text、Image、View,他们的新名字叫Turbo Modules。组件的作用是相同的,但是实现和行为会不同。第一,他们是懒加载的(只有当App需要的时候加载),而现在是在启动时全部加载。另外,他们也是通过JSI导出的,所以JS可以拿到这些组件的引用,并且在React Natvie JS里使用他们。尤其会在启动的时候带来更好的性能表现。
CodeGen -- 为了让JS侧成为两端通信时的唯一可信来源。它可以让开发者创建JS的静态类,以便Native端(Fabric和Turbo Modules)可以识别它们,并且避免每次都校验数据 => 将会带来更好的性能,并且减少传输数据出错的可能性。
Lean Core -- 是对React Native库架构的变化。目的是减轻lib的负担,并帮助社区更快地解决更多pull request。Facebook正在把库的某些部分拆分出来,通过关注Github就可以看出他们在这方面的行动了。比如这个:
顺便一提,这里有个例子,你可以在Chrome里尝试一下,这也是JSI如何工作和导出对象的灵感来源:
在Chrome里打开开发者界面,然后输入console.log,回车。你将看到native code。这说明console.log其实是一个Native的函数。
在Chrome里打开开发者界面,然后输入console.log,回车。你将看到native code接下来我们再对比一下使用新架构,App启动的流程是怎么样的。
用户点击App的图标
Fabric加载Native侧(没有Native组件)
然后通知JS线程Native侧准备好了,JS侧会加载所有的main.bundle.js,里面包含了所有的js和react逻辑+组件
JS通过一个Native函数的引用(JSI API导出的)调用到Fabric,同时Shadow Node创建一个和以前一样的UI树。
Yogo执行布局计算,把基于Flexbox的布局转化成终端的布局。
6. Fabric执行操作并且显示UI==>
img为了完成整个流程,我们几乎做了同样的事情,但是没有了Bridge,现在我们可以有更好的性能,我们可以用同步的方式进行操作,甚至可以对UI上的同步操作进行优先级排序。启动时间也将更快,App也将更小。
所以,我们什么时候可以上手使用这些东西?
可以在Github上关注它们各自的更新情况:
JSI
Fabric
Turbo Modules
Lean Core
CodeGen
按理说,大多数的更改将逐步完成,并有希望在2019第四季度或2020第一季度发布。
部分参考文档,便于更深刻地理解:
Latest RN news from Core Team- https://facebook.github.io/react-native/blog/
Parashuram post about the new architecture http://blog.nparashuram.com/2019/01/react-natives-new-architecture-glossary.html
Pharam new 2019 lecture React Amsterdam — https://www.youtube.com/watch?v=NCLkLCvpwm4
https://levelup.gitconnected.com/wait-what-happens-when-my-react-native-application-starts-an-in-depth-look-inside-react-native-5f306ef3250f
Layout engine explnation — https://www.freecodecamp.org/news/how-react-native-constructs-app-layouts-and-how-fabric-is-about-to-change-it-dd4cb510d055/
Four parts article about the new architecture by Lorenzo from Formidable (part 1 here links to the others)— https://formidable.com/blog/2019/react-codegen-part-1/
FB State of React Q4 2018 by Sophie Alpert from FB team at this time — http://facebook.github.io/react-native/blog/2018/06/14/state-of-react-native-2018