React Native Internals(翻译)

2019-03-07

前言

本文源链接为 React Native Internals,我将其翻译为中文。

React Native 是一个允许开发者使用 JavaScript 构建原生应用的框架。

等等!Cordova 不是很早就这么做了吗。为啥要用RN, 有啥特别的?

RN 与基于 Cordova 的应用的主要差别在于:

  • 基于 Cordova 的应用运行在 WebView 中
  • RN 则直接访问移动操作系统提供的原生 API 和 View
  • 因此,RN 应用拥有与原生应用一致的体验与性能

首先,直观会设想的是 React Native 将 JS 代码编译到对应的原生代码。

但是这很难实现,因为 Java 和 Objective C 都是强类型语言,而 JavaScript 是弱类型的。

React Native 没有这么做,而是通过一种更聪明的方式。

从本质上,React Native 可以看做是一组 React 组件,其中每个组件对应于相应的视图和组件。

例如,一个原生的 TextInput 会有一个 RN 组件与之对应,可以在 JS 中直接导入,就像使用普通 React 组件一样。

因此,开发者可以向开发普通 React Web APP 那样来开发原生应用。

好吧,这看起来像是黑魔法 🙄。

为了理解其中原理,让我们看看 React Native 的架构与内部工作方式。

架构 🤖

iOS 与 Android 的架构是类似的,但是有细微差异。

如果我们从整体来看,RN 平台主要分为三部分:

  1. Native Code/Modules

    • 大多数原生代码在 iOS 使用 Objective C 或 Swift 编写,在 Android 使用 Java 编写
    • 但是对于写 React Native APP 来说,我们可能都不需要写 iOS 或 Android 的原生代码
  2. Javascript VM

    • 我们的 JavaScript 代码由 JS 虚拟机运行
    • React Native 使用 JavaScriptCore 运行 iOS/Android 虚拟机和设备中的代码,JavaScriptCore 也是驱动 Safari 的 JavaScript 引擎
    • JavaScriptCore 是一个开源的 JavaScript 引擎,最初是为 WebKit 开发的
    • 在 iOS 平台上,React Native 使用系统提供的 JavaScriptCore。它最初是与 OS X Mavericks 一起被引入iOS 7 的。https://developer.apple.com/reference/javascriptcore
    • 在 Android 平台,应用会把 JavaScriptCore 打包在内。这回增大包体积。因此一个 RN 的 HelloWord 应用,在 Android 上也得 3~4 MB
    • 在 Chrome Debug 模式的时候,JavaScript 代码会在 Chrome 中运行(而不是设备上的 JavaScriptCore),并且通过 WebSocket 与原生代码通信。因此这会使用 V8 引擎。这样,我们就能使用 Crhome 的调试工具看到许多信息,比如网络请求,命令行日志等等。😎
  3. React Native Bridge:React Native Bridge 是 C++/Java bridge,负责 Native 线程与 Java 线程间的通信。使用定制的协议传递消息。

    在大多数情况下,开发者会使用 JavaScript 来编写整个应用。

    通过以下命令行语句来运行程序:

    react-native run-ios
    
    react-native run-android
    

    此时,React Native Cli 会创建一个 node 打包器(metro bundler),它会将 JS 代码打包为一个单个的 main.bundle.js 文件。

    这个打包器可以被看做是类似 Webpack。

    现在,当 React Native 应用被启动时,首先被加载的是原生入口点(native entry point)。

    Native 线程会创建 JS VM 线程,并运行打包后的 JS 代码。JS 代码中包含应用的所有业务逻辑。

    Native 线程现在通过 RN Bridge 发送消息,来启动 JS 应用。

    指令包括加载哪些视图,从硬件中获取哪些信息等等。

    例如,如果 JS 线程想要创建一个 VIew 和 Text,它会将请求打包为一个单条的消息,发送到 Native 线程并进行实际展示。

    [ [2,3,[2,'Text',{...}]] [2,3,[3,'View',{...}]] ]
    

    Native 线程会处理这些操作,并将结果返回给 JS,以确保操作已被完成执行。

    注意:如果想在 Console 中查看 bridge 消息,只需要在 index.<platform>.js 中加入以下代码:

    import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue';
    MessageQueue.spy(true);
    

线程模型 🚧

当 React Native 应用被启动时,它会创建一下线程队列:

  1. Main thread (Native Queue)
    • 这是应用启动时就立刻创建的主线程
    • 它加载 APP 并启动 JS 线程来执行 JavaScript 代码
    • Native 线程还监听 UI 事件,例如”按下“、”触摸“等等。这些事件会通过 RN Bridge 被传递到 JS 线程
    • 一旦 JavaScript 加载起来,JS 线程会通过消息方式发送屏幕上要展示的内容
    • 这个信息被 shadow node thread 使用,来计算布局
    • shadow thread 从根本上说是一个数学引擎,来最终决定视图的位置该如何计算
    • 这些指令最终传回主线程,并进行实际展示
  2. Javascript thread (JS Queue)
    • JavaScript 线程队列是主 JS 包被执行的线程
    • JS 线程执行所有的业务逻辑,也就是我们编写的 React Native 业务代码
  3. Custom Native Modules
    • 除了 React Native 创建的线程外,我们还可以在自己创建的自定义模块中创建线程,以加快应用程序的性能
    • 例如:动画在 React Native 中交由一个单独的 native 线程来处理,以分担 JS 线程的工作

链接:https://www.youtube.com/watch?v=0MlT74erp60

View Managers 👓

View Managers 是 Native Module,它将 JSX 视图映射到 Native Views。例如:

import React, { Component } from "react";
import { Text, View, AppRegistry } from "react-native";

class HelloWorldApp extends Component {
  render() {
    return (
      <View style={{ padding: 40 }}>
        <Text>Hello world!</Text>
      </View>
    );
  }
}

export default HelloWorldApp;
AppRegistry.registerComponent("HelloWorldApp", () => HelloWorldApp);

其中:

  • 当我们写下 <Text/>,如果实在 Android 平台,Text View manager 会调用 new TextView(getContext())

在 Android 平台,View Managers 都是继承自 ViewManager 的类,在 iOS 则是继承自 RCTViewManager

开发模式 🔨

当 APP 在 DEV 模式运行时 JavaScript 线程在开发机(电脑)上被创建。

尽管 JS 代码在电脑上运行的性能比手机上高,但是你会注意到实际性能比 bundled 模式或者 production 模式还要慢。

这是因为在 DEV模式下,在运行时要做许多额外工作,来提供更好的警告与错误信息,例如验证 propTypes 以及其它的断言。

此外,设备与 JS 之间的通信延迟也是导致慢的原因。

链接:https://www.youtube.com/watch?v=8N4f4h6SThc - RN android architecture

React Native 的新架构(Fabric)

React Native 团队目前正在为 React Native 开发一个新架构。

新架构的代号为 Fabric,将会允许 React Native 以同步方式执行高优先级的 UI 更新。

这意味着 UI 在某些极限情况(Edge Cases)下会更灵敏(比如滚动视图)。

想要了解 Fabric 以及它到底如何提升 React Native 的话,请观看 Parashuram N 在 React Conf 2018 上的精彩演讲。https://www.youtube.com/watch?v=UcqRXTriUVI