React Native

应用

怎么做到真正的跨三端?

让我们自顶向下的来看看。首先我们的项目需要同时具备在app内和浏览器内使用的能力,这样才是真正的跨了三端。

我们需要做到基于react做平行扩展,这样未来小程序内无论是webview嵌h5的方式还是小程序原生的方式,都存在可以想象和优化的空间。

那么问题来了,如何做到h5和app内通用呢?

这里我们需要分析两个容器的异同点:

原生能力

App内的react native是通过nativeModule来通信;而hybird、h5、小程序等均是其他的实现,比如说jsbridge、后端接口、小程序API等等。

针对这个点,我们通过适配器模式来解决。

我们通过一个简单的模块来保存引用:


let bridge = null;

export const setBridge = bge => {
  bridge = bge;
};

export {bridge};

然后在不同的入口文件(h5、app入口文件区分开来),塞入不同的bridge即可:

// react native 入口
const {YktNativeModule} = NativeModules;
bridge.init({bridge: YktNativeModule});
setBridge(bridge);

// h5入口
bridge.init({yktlog: window.YktTracker});
setBridge(bridge);

UI跨端

我们书写的react-native组件,比如说View、Text等,需要通过react-native-web来变成react-dom可以识别的节点:

它的原理就是把react-native这个库所有暴露的api,都实现了一遍,然后配合webpack的alias,在打包的时候别名替换一下即可。

  resolve: {
    alias: {
      'react-native': 'react-native-web',
    },
  },

如何进行拆包?

答案就是拆包:

左上角的图片是整体的思路,可以看到左边是之前的现状,就是每个rn业务模块,都依赖了一些公共的代码,比如说react、react-native,我们想把这些公共的代码抽出来,不让每个业务包都含有大量重复代码,影响app的尺寸,同时客户端也可以预加载公共包的逻辑,从而降低白屏时间。

metro是Facebook出品的react native专用打包工具

整个流程分为3步。

第一步是找到公共包依赖了哪些js文件。这里我们通过metro的api,在公共包打包的createModuleFactory的阶段,拿到每个参与打包的js模块路径,然后通过md5文件路径的方式生成唯一的id。

第二步是把依赖列表保持到文件里,具体内容可以看右上角的图片,主要是文件路径和对应的模块id。

第三步是在业务包打包的时候,通过metro的api,在processModuleFilter阶段,基于第二步生成的文件,将这些模块排除在打包过程之外,从而得到纯粹的业务包内容。

这样即使日后公共包内容变多了,也可以自动计算出依赖列表,并通过唯一id去重。

一起看看拆包对bundle尺寸大小的降低:

原理

说下react-native的原理?

react-native 运行了一个带js引擎的线程,线程负责js和原生的通信, 最后通过调用原生渲染从而实现跨平台的能力,react-native的产物是一个js文件,所以可通过更新js文件即可实现热更新。

绿色的是我们应用开发的部分,我们写的代码基本上都是在这一层。

蓝色代表公用的跨平台的代码和工具引擎,一般我们不会动蓝色部分的代码。

黄色代表平台相关的 bridge 代码,做定制化的时候会添加修改代码。

红色代表系统平台的功能,另外红色上面有一个虚线,表示所有平台相关的东西都通过 bridge 隔离开来了,红色部分是独立于 React Native 的。

参考:

React Native 原理与实践 - 知乎

原生端和js端是怎么通信的?

旧架构通过bridge,App启动时,原生侧会将原生模块(module)注册到js的映射表,原生模块包括方法(method),即一个module对应多个method的key-value形式。js在调用的时候,通过module + method的方式进行序列化发送到原生,原生解析module得到具体的原生对象,再通过method找到对应的方式,和JSBridge的本质一样,只是做了一层封装。并且,js端自行维护了消息队列实现调用回调。

新架构采用JavaScript Interface (JSI)。

JSI 移除了原生代码和JavaScript代码之间的桥接(bridge),同时也省去了两端相互调用时大量的JSON序列化和反序列化操作。JSI为原生和JS交互打开了新的大门。

不同于之前直接将 JavaScript 代码输入给 JSC,JSI屏蔽 JavaScript 引擎的差异,允许换用不同的 JavaScript 引擎,同时JSI 所在的 C++层也可以作为复用 Native 代码的一种方式。

参考:

React Native 原理与实践 - 知乎

Hermes引擎相比javascriptCore的优势

关于hermes做了哪些优化,以及生产环境使用后效果提升如何,可以参考头条这篇文章ReactNative在游戏营销场景中的实践和探索-Hermes引擎 - 掘金

这里我们只看两点,一个是hermes可以直接运行字节码;一个是大大降低的内存占用和启动时间。

可以看到启用了hermes和提供字节码后,TTI(可交互时间降低了一半左右):