Facebook在2018年6月官方宣布了大规模重构React Native的计划及重构路线图。目的是为了让React Native更加轻量化、更适应混合开发,接近甚至达到原生的体验。(也有多是React Native团队感觉到了Google Flutter的追赶压力,必须从架构上作出重大革新,将来才有可能和Flutter进行全面的竞争)。从Facebook公布的官方信息来看,这是一次革命性的架构重构,主要的重构内容以下:前端
目前业内React Native框架已经有了普遍的应用。京东在这个方面起步比较早,相对来讲总体解决方案也比较成熟。目前京东深度定制和扩展的JDReact解决方案已经累计接入了200+个RN业务和20+的独立APP,而且承担了千万级的DAU。从业务实际开发中仍是遇到了很多坑,其中性能问题比较明显,具体有如下几类问题: 加载性能偏慢,由于系统或者自定义的原生UI组件和API的注册加载过程当中须要验证全部属性和JS API的一致性,影响加载性能,甚至直接致使主UI线程很容易阻塞。 JSBridge,React Native总体生命周期和JSBridge绑定太紧,全部的原生和JS之间操做所有是经过这个Bridge,并且每次的事件通信是有时间间隔的,致使总体渲染过程是异步的。 手势问题,React Native目前的架构,从JS侧很难解决不少复杂的手势问题,须要从新定制SDK来解决问题。 返回事件的处理,目前的返回事件不能像原生同样,在组件中监听。 Layout的计算,总体的UI计算必需要在shadow layout中完成,没有办法在总体的平台框架中计算。java
现有的Native & JS Component组件以下,经过这些组件能够完成原生UI渲染和API调用。这些组件都是经过packageManger注册到系统的,当RN业务启动后,须要对总体的属性和方法作一些校验,存在性能损失;另外RN是容许多个packagemanger同时注册的,当API数量偏大时,致使的问题须要循环遍历,调用过程也存在性能损耗。node
因此目前的架构下这些组件和API太过依赖JSBridge的初始化,并且通信能力也局限于这一条通道。从渲染的层次来看,React Native是多线程运行的,最多见的是JS线程和原生端的线程,一旦线程间异常,JSBridge总体将会阻塞,咱们常常也能看到JS运行异常了,实际JS线程已经无响应了,但原生端还能响应滚动事件。react
针对先有框架的一些问题Facebook在最近的版本中尝试过不少优化工做,从2013年发布到目前已经更新到了V0.58,去年一年发布了10多个版本。从版本更新能够看出,除了一些组件的更新和BUG修复外,Facebook作了性能优化方面的尝试,让其在加载和渲染性能上尽量的达到原生。c++
重大性能优化的版本: 0.33 Lazy module 0.40 RAM bundle/unbundle 0.43 FlatList/SectionList/VirtualizedList 0.50 SwipeableFlatList/Fibernpm
如下是目前官方建议的一些优化性能的方案:react-native
![]() |
![]() |
---|
因此咱们的结论是,在现有架构下的各类优化都很难完全解决性能问题。promise
在最近的开发者大会中,Facebook对下一代架构重构的进展进行了介绍,咱们也对master分支上提交的部分源码进行了分析,能够了解新架构的一些雏型设计,总体架构还在不断优化中,相信还会有更多惊喜。从现有的信息和代码来看,JS层业务的影响较小,不会所以次大规模架构重构后须要大量适配业务代码。此次的重构主要是JSBridge及原生架构的重构,下面咱们从几个层面对比介绍总体框架:缓存
UI的渲染过程分为三层:JS业务层、shadow tree、原生UI层。其中JS和shadow tree是经过JSBridge来同步数据的,JS层会将全部UI node生成一串JSON数据,传递到原生shadow层,原生shadow层经过传入node数据,新增新UI或者删除一些不须要的UI组件,这就完成了下图这三个层次之间的驱动关系:安全
带来的问题是总体UI渲染异步且太过于依赖JSBrige,很容易出现阻塞而影响总体UI体验,从JDReact的业务开发经验来看,好比初始化过程当中UI复杂度太高,点击UI时响应时间会很长,就是由于UI被阻塞了很难响应touch事件,另外UI大小计算JS framework没有办法直接计算,须要依赖原生计算完成后的回调。
再看看SrollView的例子,这是业务或者社区反馈性能和体验问题最大的组件。最第一版本的ScollView是一次渲染的不做任何回收,因此启动性能慢且内存占用较大。后续版本Flatlist做了组件的回收,内存基本稳定了,可是快速滑动过程当中出现了体验问题,容易白屏且容易卡顿。你们看下面的流程图就能明白为何Flatlist(基于ScollView实现)/ScrollView 快速滑动下会有长时间的白屏或者卡顿。
在Flatlist快速滑动过程当中JS层会根据滑动的事件,触发Flatlist item的render渲染每一条数据,可是由于JSBridge的异步关系致使了shadow层最终呈现到原生的UI是异步的,并且滑动太快后会有大量的UI事件会阻塞在JSBridge,也致使了长时间的白屏出现,同时当部分item滑出可视区域必定的范围后UI内容会被回收等待下次滑到该区域后从新渲染。
UI的渲染过程仍是和以前介绍的架构同样分为三层:JS业务层、shadow tree、原生UI层。解耦了JS层到shadow层对于JSBridge的过分依赖,其中JS和shadow tree是经过新架构JSI(后面会介绍原理)来同步数据的,能够作到对单个node组件的同步更新,这样JS render到原生能够采用同步操做也能采用异步操做,同时由于shadow层和JS层是一一对应的,因此能够作到对UI更细的控制,大概的原理图以下:
回到以前ScrollView的例子,看看Fabric是怎么解决快速滑动过程当中的性能问题的。
初始化:JS到Shadow层已是同步操做了,而shadow层到原生UI变成了能够异步也能够同步操做了,组件能够根据本身的业务场景来适配不一样的操做。
滑动过程:原生端直接控制JS层渲染,同时建立shadow层的node节点,node节点会采用同步的方式渲染原生UI,整个过程当中滑动的渲染是采用的同步操做,因此不会出现旧架构下白屏的问题。
从这些组件的结构描述来看,新的Fabric架构大体以下:
下面咱们参考下目前Facebook开放出来的部分代码:
总体的Fabric的UIManger 组件和消息通道是怎么创建的呢?你们能够参考文件Scheduler.cpp,JS会经过JSI调用该接口来初始化。
下面咱们看看JS端是如何生成原生组件的,你们能够对照源码,在JS端咱们有FabricUIManager,在初始化UIManagerBinding过程当中,注册到运行的JS环境,由于UIManagerBinding是JSI实现的,因此能够理解为咱们建立了一个Host代理对象,注册到了JS,而JS侧也对应一样的数据结构来一一对应。
下面是建立一个node的例子:
从目前的结构来看,后续Fabric UI开发,须要从C++ component层、shadow层、原生Java层,三个层次开发,并且建立的shadow层也是经过JSI的方式和JS层的node节点一一对应的。
上面介绍Fabric架构时提到了JSI,那到底什么是JSI呢?如何能作到更原子级的控制每一个模块和API呢?他是架起JS和原生java或者Objc的桥梁,相似于现有架构的JSBridge,可是采用的是内存共享、代理类的方式,JSI全部的运行环境都是在JSCRuntime环境下的,为了实现和JS端直接通信,咱们须要在C++层实现一个JSI::HostObject,该数据结构只有get、set两个接口,经过prop来区分不一样接口的调用。
而后经过JSI接口生成一个JSObject,能够看到生成的代理对象和咱们的HostObjectProxy是共享内存的,而且proxy中也实现了set和get方法。如下是具体的流程:
在JS端对应的是LazyObject,经过对这些Object的set、get,来完成对应C++实现的hostobject方法的调用:
这是基于新的JSI架构实现的Native module架构。JS层经过JSI获取到对应的C++层的module对象的代理,最终经过JNI回调Java层module。 C++层NativeMoudleProxy是经过JSI实现的对象,能够经过它传入module的名字获取C++层注册的module,已经这个module封装的全部的API method name。因此在JS业务加载的时候,会将这个proxy生成JS object代理对象到JS层。
2.JS层经过getNativeModule API并传递prop到JSI,最终会经过JSI接口找到Host object NativeModuleProxy,因NativeModuleProxy主要做用是将注册在C++层的JSI module经过名字生成JS Object传递的JS层调用, 因此其get方法中只有一个属性,就是经过JSINativeModules获取对应的module,而JSINativeModules是有缓存机制的,若是没有缓存的就直接解析该module中全部的API,若是有直接读取缓存的module信息.
你们能够看到在解析过程当中,新版本增长了同步和异步的方法,也就是promise和sync。因此JSI module实际是能够同步操做API的,不像以前JSBridge的API都是异步操做的,同步操做的好处就是能作到线程间的同步。
全部的JSI module都是经过JSIMoudleregistry来注册的,固然这里注册的都是C++层的moulde,而全部的C++module最终是经过descriptor绑定到java层的turbomoudle中注册的Java层module,也就是咱们最终在原生端实现的API,因此C++层module会经过对应的method prop来触发Java层的方法调用。
而C++层Native module是在java层instance manger初始化过程当中注册的,遍历并注册java层和C++module。
全部理解Native Module的调用实际就是JSI的调用,而运行返回结果是基础的数据类型或者JSI object的,因此一个turbo module的method调用,返回值是能够是JSI object。开发者能够根据本身业务须要,将一些完整的数据结构封装成JSI host object,这样就能够作到,JS端一样能够获取到该对象,并同原生端对象造成了代理关系,能够同步完成一系列该Object porp功能操做,举个例子: 之前的调用方式经过JSBridge获取到一个picture,这个数据类型对应到JS端只能是一些基础的数据类型,好比咱们参见的图片地址String类型,因此若是如今要上传这个图片的话,咱们将JS端的数据再回传到原生端,以下图:
但有了JSI后就不同了,咱们在JS端透过JSI获取到的是JS Object,也就是picture,可是这个picture再也不是简单的数据类型,而是和原生端造成绑定关系的结构,可以支持不少属性的同步设置,如改变alpha值等等,会直接触发host object的属性和函数调用,因此咱们再也不须要像以前同样改变alpha须要不少的JSBridge的调用,一样上传过程能够直接操做c++层的Object执行上传操做。
下面简单列举一下相关层次的调用关系:
React Native 目前有52个直接依赖包,而后这些包间接递归依赖了589个包,你们能够在http://npm.anvaka.com/#/view/2d/react-native网站看看总体的依赖关系,是一个很是复杂的关系图。 从目前React Native开源的代码来看,总体是个很大的repo工程,包含了各类各样的Native组件、API、JS module,如何让这些组件和API维护更简单,让接入React Native架构的APP能将API或者组件快速裁剪变小也成了此次架构重构的目标。 删除掉一些不须要的modules,将全部的module分离成小的repo,相似于组件化模块。 减少业务开发的bundle代码的大小(按需引入和编译须要的component),进而提高业务的渲染和启动速度。 分离后将这些组件或API开放给社区,社区能够贡献本身的资源,这样可让组件的维护和迭代更快,帮助减小组件维护成本。 减小目前React Native SDK开发的依赖,简化代码结构,让SDK升级起来更轻量、更快速。 增长了社区的贡献让PR的修复加快,让更多的开发者集中在更合理的位置,减小重复开发,下降开放的复杂度。 将来facebook计划:
之后原生端Native组件主要分三部分,一部份会放到系统的SDK,一部分移到开源社区维护,开发者再贡献一部分组件。对开发者而言带来的影响:
咱们经过源码分析给你们简单介绍了Facebook的React Native下一代框架的设计,相信无论从性能体验和功能上都会有很大的变化。虽然总体变化很大但对于前端开发者而言JS的变化微乎其微,而重点改造在原生端组件和API架构,封装起来变得更加复杂,须要封装C++ shadow层,因此从之前的JAVA开发扩展到了C++和JAVA开发,对于开发者知识结构和储备要求更高,但对于提高性能而言,这些都是值得的。社区化运营后Facebook官方能够从之前的组件和框架一块儿开发,简化到只需关注总体框架能力和性能了,让开发者贡献维护如今组件,大大提升了框架的迭代周期。 京东多端融合技术团队也会持续保持关注,待React Native新架构稳定后也会对渲染引擎进行升级并引入新的架构和特性。将来也会打通JDReact和JDFlutter双引擎的底层和组件,提供更为全面的跨端解决方案。