欢迎转载,请支持原创,保留原文链接:blog.ilibrary.me

(2018-10-26)

(2018-06-28, 更新) 这是一个深坑, 慎入!

本来是计划花半年时间写一本深入讲解react native技术细节的书的,内容(见下面TOC)和年度计划都拟好了,结果人算不如天算,计划拟好以后精力就转移到公司的情报挖掘(机器学习)项目上去了,移动端的代码已经接近两年没有怎么参与贡献了。

鉴于对这个感兴趣的同学好像还挺多的,我会间断性的继续补充内容,时间无保障。

本文末尾有非常多的参考链接,都是非常宝贵的资源,建议大家点开看看。

TOC(内容规划)

下面是我原计划要大写特写的内容,现诚招壮士一起做这个伟大的事业!欢迎加微信:evil_eve_live

  1. 简介
    1. 术语、简写约定
    2. React Native简介
    3. ReactJS 简介
    4. JavascriptCore简介
    5. 浏览器工作原理
  2. React Native架构
    1. React Native、React和JavascriptCore的关系
    2. React Native主要组件
  3. React Native 初始化
    1. 原生代码初始化
    2. Javascript环境初始化
    3. NativeModules加载
    4. NativeModules懒加载
  4. React Native JS接口兼容(Polyfills)
    1. fetch替换
    2. CommonJS Require
    3. alert替换
    4. console.warning替换
    5. console.error替换
  5. 线程讲解
    1. JS单线程和其背后的Native多线程
    2. JS的异步
    3. 三大线程
    4. 消息通信
  6. 深入Bridge
    1. Bridge模块简介
    2. Bridge
    3. BridgeFactory
    4. JSCExecutor
    5. MessageQueue
  7. Native调用JS代码
    1. 一般JS调用
    2. ReactNative里面的JS调用
    3. Native实现Promise
  8. 原生对象管理
    1. 模块管理
    2. UIManager
    3. TagID
  9. JS调用Native代码
    1. JS调用NativeModule代码
    2. JS回调Native代码
  10. 渲染
    1. ReactJS渲染机制
    2. ReactFiber/ReactStack
    3. ReactNative渲染
  11. 热加载
  12. JS Reload
  13. JS代码打包
    1. metro-bundler
    2. JSX
    3. babel
  14. Websocket
  15. YellowBox详解
  16. RedBox详解
  17. 调试菜单
  18. 自定义一个原生控件

术语、简写约定

名词/缩写 解释
React Native Facebook出的跨平台应用构建框架
ReactJS Facebook出的Web UI JS框架,具有革新性的编程模式
React 若无特殊说明,React就是ReactJS
NodeJS 基于JavaScript V8 Engine 的一个javascript runtime
RN React Native的缩写
N2J Native to JavaScript call
J2N JavaScript to Native call
JavaScript 我们常说的JavaScript脚本语言
JS JavaScript
JS Engine JavaScript脚本解释执行引擎
JavaScriptCore iOS/Android 平台上默认的JS Engine, 来源于Webkit, 无特殊说明情况下面,本文所有解释都会默认基于JavaScriptCore
JSC JavaScriptCore
JSX JavaScript的一个语言扩展,在JSX里面你可以用标记语言来描述React组件。不用JSX也可以写ReactJS,但是有JSX会高效很多
Bridge React Native里面实现JS和原生代码互相调用的模块,该词指向两个概念,一个是RCTBridge,找个用来管理Bridge的组件,第二个是所有自定义的用于RN的原生功能。所有原生功能想要在JS里面使用都需要有定制的bridge来支持,所以我们把这个定制的模块(包括native和js端)也称之为bridge

React Native简介

React Native是Facebook出品的一个革新性的跨平台UI框架,跨平台不是它最大的亮点,它背后的[React]才应该是它的神奇说在,也是它革新所在。我在另外一篇博客React Native系列(3) - props和state中有详细的分析。如果非要用一两句来总结它的伟大,那就是给把web开发中的无状态开发模式通过React实现了。 那些数不清的状态组合才是桌面应用和手机应用复杂的源头。

本文目的是通过源码分析,详细解释React Native框架的内部结构及运行原理。文章会比较长,组织上会尽可能由浅入深来讲。适合的读者对象是对React Native开发有一定基础的开发者。

分析的代码是基于0.50.3, 基于iOS平台的实现,包括原生的代码,js框架和打包器。从代码打包,react native初始化,js加载到运行以及错误处理。安卓平台的Java代码解读不在本文覆盖范围。

ReactJS 简介

ReactJS是一个非常具有革新性的web UI框架,非常简单易用。它的virtual dom和data driven编程模式对现有的UI编程模式是一种颠覆, 极大简化了UI应用复杂状态的管理,非常值得大家去试一下。ReactJS配上Redux, 你会发现做复杂多状态的应用居然可以如此简单! React Native强大的原因就在于它是基于ReactJS的。相信读这篇文章的人大部分以对ReactJS有一定的了解,这里就不多说。

JavaScriptCore

讲React Native之前,了解JavaScriptCore会有帮助,也是必要的。React Native的核心驱动力就来自于JS Engine. 你写的所有JS和JSX代码都会被JS Engine来执行, 没有JS Engine的参与,你是无法享受ReactJS给原生应用开发带来的便利的。在iOS上,默认的就是JavaScriptCore, iOS 7之后的设备都支持. iOS 不允许用自己的JS Engine. JavaScriptCore来自于WebKit, 所以,安卓上默认也是用JavaScriptCore.

所以,你深入了解React Native的第一站应该是 JavaScriptCore.

nshipster.cn有一篇文章 对JavaScriptCore的使用有一些简单的使用说明。读完这篇文章你会发现,JavaScriptCore使用起来是非常简单的,这得益于JavaScript的简洁设计。所以,本文不会花精力讲解JavaScriptCore。

相信有JavaScript和OC基础的同学只需要花大概30秒就可以把上面的这篇文章读完。

恭喜你! 当你读完上面的那篇文章以后,你已经掌握了自己创造React Native框架的核心技术!一点也不夸张,JavaScriptCore在iOS平台上给React Native提供的接口也仅限于那几个接口,你弄明白了JavaScriptCore那几个接口, React Native 剩下的魔法秘密都可以顺藤摸瓜来分析了。

本文接下来要讲解的就是Facebook围绕这几个接口以及用一个React来颠覆整个native开发所做的精妙设计和封装。你如果想自己做一个基于JS Engine做一个类似React Native的框架出来,建议研究JSPatch, 另外,还有一个外国朋友写了一个博客讲解用Edge JS引擎自己动手写一个bridge. React Native的封装非常庞大,涉及了很多的话题,直接与JS Engine相关的不多。

浏览器工作原理

浏览器通过Dom Render来渲染所有的元素.

浏览器有一整套的UI控件,样式和功能都是按照html标准实现的。

浏览器能读懂html和css。

html告诉浏览器绘制什么控件(html tag),css告诉浏览器每个类型的控件(html tag)具体长什么样。

浏览器的主要作用就是通过解析html来形成dom树,然后通过css来点缀和装饰树上的每一个节点。

UI的描述和呈现分离开了。

  1. html文本描述了页面应该有哪些功能,css告诉浏览器该长什么样。
  2. 浏览器引擎通过解析html和css,翻译成一些列的预定义UI控件,
  3. 然后UI控件去调用操作系统绘图指令去绘制图像展现给用户。
  4. Javascript可有可无,主要用于html里面一些用户事件响应,DOM操作、异步网络请求和一些简单的计算。

在react native 里面,1和2是不变的,也是用html语言描述页面有哪些功能,然后stylesheet告诉浏览器引擎每个控件应该长什么样。并且和浏览器用的是同一个引擎。

在步骤3里面UI控件不再是浏览器内置的控件,而是react native自己实现的一套UI控件(两套,android一套,ios一套),这个切换是在MessageQueque中进行的,并且还可以发现,他们tag也是不一样的。

Javascript在react native里面非常重要,

  1. 它负责管理UI component的生命周期,管理Virtual DOM
  2. 所有业务逻辑都是用javascript来实现或者衔接
  3. 调用原生的代码来操纵原生组件。
  4. Javascript本身是无绘图能力的,都是通过给原生组件发指令来完成

React Native 架构

先上一副React Native 架构图,这是我在内部培训的时候画的一副图。

  1. 绿色的是我们应用开发的部分。我们写的代码基本上都是在这一层。
  2. 蓝色代表公用的跨平台的代码和工具引擎,一般我们不会动蓝色部分的代码。
  3. 黄色代码平台相关的代码,做定制化的时候会添加修改代码。不跨平台,要针对平台写不同的代码。iOS写OC, android写java,web写js. 每个bridge都有对应的js文件,js部分是可以共享的,写一份就可以了。如果你想做三端融合,你就得理解这一个东西。如果你要自己定制原生控件,你就得写bridge部分。
  4. 红色部分是系统平台的东西。红色上面有一个虚线,表示所有平台相关的东西都通过bridge隔离开来了。
  5. 大部分情况下我们只用写绿色的部分,少部分情况下会写黄色的部分。你如果对基础架构和开源感兴趣,你可以写蓝色部分,然后尝试给那些大的开源项目提交代码。红色部分是独立于React Native的,不讨论。

React Native架构

React Native、React和JavascriptCore的关系

React Native最重要的三个概念应该就是React Native、React和JavascriptCore.

理解这三者的关系以后大家就可以自己去深入研究React Native了。

React是一个纯JS库,所有的React代码和所有其它的js代码都需要JS Engine来解释执行。因为种种原因,浏览器里面的JS代码是不允许调用自定义的原生代码的,而React又是为浏览器JS开发的一套库,所以,比较容易理解的事实是React是一个纯JS库,它封装了一套Virtual Dom的概念,实现了数据驱动编程的模式,为复杂的Web UI实现了一种无状态管理的机制, 标准的HTML/CSS之外的事情,它无能为力。调用原生控件,驱动声卡显卡,读写磁盘文件,自定义网络库等等,这是JS/React无能为力的。

你可以简单理解为React是一个纯JS 函数, 它接受特定格式的字符串数据,输出计算好的字符串数据。

JS Engine负责调用并解析运行这个函数。

React Native呢? 它比较复杂。复杂在哪里?前面我们说了React 是纯JS库,意味着React只能运行JS代码,通过JS Engine提供的接口(Html Tag)绘制html支持的那些元素,驱动有限的声卡显卡。简单点说, React只能做浏览器允许它做的事情, 不能调用原生接口, 很多的事情也只能干瞪眼。

React Native它可不一样。

第一点,驱动关系不一样。前面我们说的是, JS Engine来解析执行React脚本, 所以,React由浏览器(最终还是JS Engine)来驱动. 到了React Native这里,RN的原生代码(Timer和用户事件)驱动JS Engine, 然后JS Engine解析执行React或者相关的JS代码,然后把计算好的结果返回给Native code. 然后, Native code 根据JS计算出来的结果驱动设备上所有能驱动的硬件。重点,所有的硬件。也就是说,在RN这里,JS代码已经摆脱JS Engine(浏览器)的限制,可以调用所有原生接口啦!

第二点, 它利用React的Virtual Dom和数据驱动编程概念,简化了我们原生应用的开发, 同时,它不由浏览器去绘制,只计算出绘制指令,最终的绘制还是由原生控件去负责,保证了原生的用户体验。

React组件结构(图文)

React Native组件结构(图文)

驱动硬件的能力决定能一个软件能做多大的事情,有多大的主控性。研究过操作系统底层东西或者汇编的同学明白,我们大部分时候写的代码是受限的代码,很多特权指令我们是没法使用的,很多设备我们是不允许直接驱动的。我们现在的编程里面几乎已经没有人提中断了,没有中断,硬件的操作几乎会成为一场灾难.

在一定程度上,React Native和NodeJS有异曲同工之妙。它们都是通过扩展JavaScript Engine, 使它具备强大的本地资源和原生接口调用能力,然后结合JavaScript丰富的库和社区和及其稳定的跨平台能力,把javascript的魔力在浏览器之外的地方充分发挥出来。

JavaScriptCore + ReactJS + Bridges 就成了React Native。

  1. JavaScriptCore负责JS代码解释执行
  2. ReactJS负责描述和管理VirtualDom,指挥原生组件进行绘制和更新,同时很多计算逻辑也在js里面进行。ReactJS自身是不直接绘制UI的,UI绘制是非常耗时的操作,原生组件最擅长这事情。
  3. Bridges用来翻译ReactJS的绘制指令给原生组件进行绘制,同时把原生组件接收到的用户事件反馈给ReactJS。
  4. 要在不同的平台实现不同的效果就可以通过定制Bridges来实现。

#深入 Bridge 前面有提到, RN厉害在于它能打通JS和Native Code, 让JS能够调用丰富的原生接口,充分发挥硬件的能力, 实现非常复杂的效果,同时能保证效率和跨平台性。

打通RN任督二脉的关键组件就是Bridge. 在RN中如果没有Bridge, JS还是那个JS,只能调用JS Engine提供的有限接口,绘制标准html提供的那些效果,那些摄像头,指纹,3D加速,声卡, 视频播放定制等等,JS都只能流流口水,原生的、平台相关的、设备相关的效果做不了, 除非对浏览器进行定制。

Bridge的作用就是给RN内嵌的JS Engine提供原生接口的扩展供JS调用。所有的本地存储、图片资源访问、图形图像绘制、3D加速、网络访问、震动效果、NFC、原生控件绘制、地图、定位、通知等都是通过Bridge封装成JS接口以后注入JS Engine供JS调用。理论上,任何原生代码能实现的效果都可以通过Bridge封装成JS可以调用的组件和方法, 以JS模块的形式提供给RN使用。

每一个支持RN的原生功能必须同时有一个原生模块和一个JS模块,JS模块是原生模块的封装,方便Javascript调用其接口。Bridge会负责管理原生模块和对应JS模块之间的沟通, 通过Bridge, JS代码能够驱动所有原生接口,实现各种原生酷炫的效果。

RN中JS和Native分隔非常清晰,JS不会直接引用Native层的对象实例,Native也不会直接引用JS层的对象实例(所有Native和JS互掉都是通过Bridge层会几个最基础的方法衔接的)。

Bridge 原生代码负责管理原生模块并生成对应的JS模块信息供JS代码调用。每个功能JS层的封装主要是针对ReactJS做适配,让原生模块的功能能够更加容易被用ReactJS调用。MessageQueue.js是Bridge在JS层的代理,所有JS2N和N2JS的调用都会经过MessageQueue.js来转发。JS和Native之间不存在任何指针传递,所有参数都是字符串传递。所有的instance都会被在JS和Native两边分别编号,然后做一个映射,然后那个数字/字符串编号会做为一个查找依据来定位跨界对象。

Bridge各模块简介

本节介绍以下模块

  1. RCTRootView,
  2. RCTRootContentView,
  3. RCTBridge,
  4. RCTBatchedBridge,
  5. RCTJavaScriptLoader,
  6. RCTContextExecutor,
  7. RCTModuleData,
  8. RCTModuleMethod,

RCTRootView

RCTRootView是React Native加载的地方,是万物之源。从这里开始,我们有了JS Engine, JS代码被加载进来,对应的原生模块也被加载进来,然后js loop开始运行。 js loop的驱动来源是Timer和Event Loop(用户事件). js loop跑起来以后应用就可以持续不停地跑下去了。

如果你要通过调试来理解RN底层原理,你也应该是从RCTRootView着手,顺藤摸瓜。

每个项目的AppDelegate.m- (BOOL)application:didFinishLaunchingWithOptions:里面都可以看到RCTRootView的初始化代码,RCTRootView初始化完成以后,整个React Native运行环境就已经初始化好了,JS代码也加载完毕,所有React的绘制都会有这个RCTRootView来管理。

RCTRootView做的事情如下:

  1. 创建并且持有RCTBridge
  2. 加载JS Bundle并且初始化JS运行环境.
  3. 初始化JS运行环境的时候在App里面显示loadingView, 注意不是屏幕顶部的那个下拉悬浮进度提示条. RN第一次加载之后每次启动非常快,很少能意识到这个加载过程了。loadingView默认情况下为空, 也就是默认是没有效果的。loadingView可以被自定义,直接覆盖RCTRootView.loadingView就可以了.开发模式下RN app第一次启动因为需要完整打包整个js所以可以很明显看到加载的过程,加载第一次以后就看不到很明显的加载过程了,可以执行下面的命令来触发重新打包整个js来观察loadingView的效果 ` watchman watch-del-all && rm -rf node_modules/ && yarn install && yarn start – –reset-cache`, 然后杀掉app重启你就会看到一个很明显的进度提示.
  4. JS运行环境准备好以后把加载视图用RCTRootContentView替换加载视图.
  5. 所有准备工作就绪以后调用AppRegistry.runApplication正式启动RN JS代码,从Root Component()开始UI绘制。

一个App可以有多个RCTRootView, 初始化的时候需要手动传输Bridge做为参数,全局可以有多个RCTRootView, 但是只能有一个Bridge.

如果你做过React Native和原生代码混编,你会发现混编就是把AppDelegate里面那段初始化RCTRootView的代码移动到需要混编的地方,然后把RCTRootView做为一个普通的subview来加载到原生的view里面去,非常简单。不过这地方也要注意处理好单Bridge实例的问题,同时,混编里面要注意RCTRootView如果销毁过早可能会引发JS回调奔溃的问题。

RCTRootContentView

RCTRootContentView reactTag在默认情况下为1. 在Xcode view Hierarchy debugger 下可以看到,最顶层为RCTRootView, 里面嵌套的是RCTRootContentView, 从RCTRootContentView开始,每个View都有一个reactTag.

RCTRootView继承自UIView, RCTRootView主要负责初始化JS Environment和React代码,然后管理整个运行环境的生命周期。 RCTRootContentView继承自RCTView, RCTView继承自UIView, RCTView封装了React Component Node更新和渲染的逻辑, RCTRootContentView会管理所有react ui components. RCTRootContentView同时负责处理所有touch事件.

RCTBridge

这是一个加载和初始化专用类,用于前期JS的初始化和原生代码的加载。

  1. 负责加载各个Bridge模块供JS调用
  2. 找到并注册所有实现了RCTBridgeModule protocol的类, 供JS后期使用.
  3. 创建和持有 RCTBatchedBridge

RCTBatchedBridge

如果RCTBridge是总裁, 那么RCTBatchedBridge就是副总裁。前者负责发号施令,后者负责实施落地。

  1. 负责Native和JS之间的相互调用(消息通信)
  2. 持有JSExecutor
  3. 实例化所有在RCTBridge里面注册了的native node_modules
  4. 创建JS运行环境, 注入native hooks 和modules, 执行 JS bundle script
  5. 管理JS run loop, 批量把所有JS到native的调用翻译成native invocations
  6. 批量管理原生代码到JS的调用,把这些调用翻译成JS消息发送给JS executor

RCTJavaScriptLoader

这是实现远程代码加载的核心。热更新,开发环境代码加载,静态jsbundle加载都离不开这个工具。

  1. 从指定的地方(bundle, http server)加载 script bundle
  2. 把加载完成的脚本用string的形式返回
  3. 处理所有获取代码、打包代码时遇到的错误

RCTContextExecutor

封装了基础的JS和原生代码互掉和管理逻辑,是JS引擎切换的基础。通过不同的RCTCOntextExecutor来适配不同的JS Engine,让我们的React JS可以在iOS、Android、chrome甚至是自定义的js engine里面执行。这也是为何我们能在chrome里面直接调试js代码的原因。

  1. 管理和执行所有N2J调用

RCTModuleData

加载和管理所有和JS有交互的原生代码。把需要和JS交互的代码按照一定的规则自动封装成JS模块。

  1. 收集所有桥接模块的信息,供注入到JS运行环境

RCTModuleMethod

记录所有原生代码的导出函数地址(JS里面是不能直接持有原生对象的),同时生成对应的字符串映射到该函数地址。JS调用原生函数的时候会通过message的形式调用过来。

  1. 记录所有的原生代码的函数地址,并且生成对应的字符串映射到该地址
  2. 记录所有的block的地址并且映射到唯一的一个id
  3. 翻译所有J2N call,然后执行对应的native方法。
    1. 如果是原生方法的调用则直接通过方法名调用,MessageQueue会帮忙把Method翻译成MethodID, 然后转发消息给原生代码,传递函数签名和参数给原生MessageQueue, 最终给RCTModuleMethod解析调用最终的方法
    2. 如果JS调用的是一个回调block,MessageQueue会把回调对象转化成一个一次性的block id, 然后传递给RCTModuleMethod, 最终由RCTModuleMethod解析调用。基本上和方法调用一样,只不过生命周期会不一样,block是动态生成的,要及时销毁,要不然会导致内存泄漏。

注:

实际上是不存在原生MessageQueue对象模块的,JS的MessageQueue对应到原生层就是RCTModuleData & RCTModuleMethod的组合, MessageQueue的到原生层的调用先经过RCTModuleData和RCTModuleMethod翻译成原生代码调用,然后执行.

BridgeFactory

JSCExecutor

MessageQueue

这是核心中的核心。整个react native对浏览器内核是未做任何定制的,完全依赖浏览器内核的标准接口在运作。它怎么实现UI的完全定制的呢?它实际上未使用浏览器内核的任何UI绘制功能,注意是未使用UI绘制功能。它利用javascript引擎强大的DOM操作管理能力来管理所有UI节点,每次刷新前把所有节点信息更新完毕以后再给yoga做排版,然后再调用原生组件来绘制。javascript是整个系统的核心语言。

我们可以把浏览器看成一个盒子,javascript引擎是盒子里面的总管,DOM是javascript引擎内置的,javascript和javascript引擎也是无缝链接的。react native是怎么跳出这个盒子去调用外部原生组件来绘制UI的呢?秘密就在MessageQueue。

javascript引擎对原生代码的调用都是通过一套固定的接口来实现,这套接口的主要作用就是记录原生接口的地址和对应的javascript的函数名称,然后在javascript调用该函数的时候把调用转发给原生接口

React Native 初始化

React Native的初始化从RootView开始,默认在AppDelegate.m:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 里面会有RootViewd的初始化逻辑,调试的时候可以从这里入手。

React Native的初始化分为几个步骤:

  1. 原生代码加载
  2. JS Engine初始化(生成一个空的JS引擎)
  3. JS基础设施初始化. 主要是require等基本模块的加载并替换JS默认的实现。自定义require, Warning window, Alert window, fetch等都是在这里进行的。基础设施初始化好以后就可以开始加载js代码了。
  4. 遍历加载所有要导出给JS用的原生模块和方法, 生成对应的JS模块信息,打包成json的格式给JS Engine, 准确地说是给MessageQueue. 这里需要提一下的是
    1. 这里的导出是没有对象的,只有方法和模块。JS不是一个标准的面向对象语言,刚从Java转JavaScript的同学都会在面向对象这个概念上栽跟头,这里特别提醒一下。

原生代码初始化

这里讨论的主要是RN相关的原生代码和用户自定义的RN模块的原生代码的加载和初始化。原生代码初始化主要分两步:

  1. 静态加载。iOS没有动态加载原生代码的接口,所有的代码都在编译的初期就已经编译为静态代码并且链接好,程序启动的时候所有的原生代码都会加载好。这是原生代码的静态加载,iOS里面没有动态加载原生代码的概念,这也是为何没有静态代码热更新的原因。
  2. RN模块解析和注入JS。这是加载的第二步。在RootView初始化的时候会遍历所有被标记为RCTModule的原生模块,生成一个json格式的模块信息,里面包含模块名称和方法名称,然后注入到JS Engine, 由MessageQueue记录下来。原生代码在生成json模块信息的时候同时会在原生代码这边维护一个名称字典,用来把模块和方法的名称映射到原生代码的地址上去,用于JS调用原生代码的翻译。

接下来我们就一步一步详细讲解原生代码的初始化。

Javascript环境初始化

RN的初始化是从RCRootView开始的,所有的绘制都会在这个RootView里面进行(Alert除外).

RootView做的第一件事情就是初始化一个空的JS Engine。 这个空的JS Engine里面包含一些最基础的模块和方法(fetch, require, alert等), 没有UI绘制模块。 RN的工作就是替换这些基础的模块和方法,然后把RN的UI绘制模块加载并注入到JS Engine.

JS Engine不直接管理UI的绘制。

  1. 所有的绘制由原生控制的UI事件和Timer触发
  2. 影响界面刷新的事件发生以后一部分直接由原生控件消化掉,直接更新原生控件。剩下的部分会通过Bridge派发给MessageQueue,然后在JS层进行业务逻辑的计算,再由React来进行Virtual Dom的管理和更新。Virtual Dom再通过MessageQueue发送重绘指令给对应的原生组件进行UI更新。

NativeModules加载

在OC里面,所有NativeModules要加载进JS Engine都必须遵循一定的协议(protocol)。

模块(OC里面的类)需要声明为<RCTBridgeModule>, 然后在类里面还必须调用宏RCT_EXPORT_MODULE() 用来定义一个接口告诉JS当前模块叫什么名字。这个宏可以接受一个可选的参数,指定模块名,不指定的情况下就取类名。

对应的JS模块在初始化的时候会调用原生类的[xxx new]方法.

模块声明为<RCTBridgeModule>后只是告诉Native Modules这有一个原生模块,是一个空的模块。要导出任何方法给JS使用都必须手动用宏RCT_EXPORT_METHOD来导出方法给JS用.

所有的原生模块都会注册到NativeModules这一个JS模块下面去,你如果想要让自己的模块成为一个顶级模块就必须再写一个JS文件封装一遍NativeModules里面的方法。

你如果想自己的方法导出就默认成为顶级方法,那么你需要一个手动去调用JSC的接口,这个在前面章节有讲解。 不建议这样做,因为这样你会失去跨JS引擎的便利性。

你可以导出常量到JS里面去, 模块初始化的时候会坚持用户是否有实现constantsToExport 方法, 接受一个常量词典。

- (NSDictionary *)constantsToExport
{
  return @{ @"firstDayOfTheWeek": @"Monday" };// JS里面可以直接调用 ModuleName.firstDayOfTheWeek获取这个常量
}

常量只会在初始化的时候调用一次,动态修改该方法的返回值无效

所有标记为RCT_EXPORT_MODULE的模块都会在程序启动的时候自动注册好这些模块,主要是记录模块名和方法名。只是注册,不一定会初始化。

Native Modules导出宏具体使用方法见官方文档Native Modules

NativeModules懒加载

React Native的NativeModules是有延迟加载机制的。App初始化的时候

  1. React Native JS接口兼容(Polyfills)
    1. fetch替换
    2. CommonJS Require
    3. alert替换
    4. console.warning替换
    5. console.error替换
  2. 线程讲解
    1. JS单线程和其背后的Native多线程
    2. JS的异步
    3. 三大线程
    4. 消息通信
  3. Native调用JS代码
    1. 一般JS调用
    2. ReactNative里面的JS调用
    3. Native实现Promise
  4. 原生对象管理
    1. 模块管理
    2. UIManager
    3. TagID
  5. JS调用Native代码
    1. JS调用NativeModule代码
    2. JS回调Native代码
  6. 渲染
    1. ReactJS渲染机制
    2. ReactFiber/ReactStack
    3. ReactNative渲染
  7. 热加载
  8. JS Reload
  9. JS代码打包
    1. metro-bundler
    2. JSX
    3. babel
  10. Websocket
  11. YellowBox详解
  12. RedBox详解
  13. 调试菜单
  14. 自定义一个原生控件
  15. SplashScreen工作逻辑

三个线程

React Native有三个重要的线程:

  1. Shadow queue. 布局引擎(yoga)计算布局用的。
  2. Main thread. 主线程。就是操作系统的UI线程。无论是iOS还是android,一个进程都只有一个UI线程,我们常说的主线程. React Native所有UI绘制也是由同一个UI线程来维护。
  3. Javascript thread. javascript线程。 大家都知道javascript是单线程模型,event驱动的异步模型。React Native用了JS引擎,所以也必需有一个独立的js 线程. 所有JS和原生代码的交互都发生在这个线程里。死锁,异常也最容易发生在这个线程.

可以看到Shadow queue是queue而不是thread, 在iOS里面queuethread之上的一层抽象,GCD里面的一个概念,创建queue的时候可以指定是并行的还是串行的。也就是说,一个queue可能对应多个thread

As mentioned above, every module will have it’s own GCD Queue by default, unless it specifies the queue it wants to run on, by implementing the -methodQueue method or synthesizing the  methodQueue property with a valid queue.

原生对象管理

待更新

消息机制(原生代码调用)

待更新

有用的图表

内部机制

Js 调用机制

Js调用时序,原文

Call Cyle Executor Environment, 原文 Js to Java call, 出处bugly 消息循环, 出处

打包

参考

  1. react-native bridge, 解释了bridge的工作原理和作用, 所有native method导出到js都以__rct_export__打头.
  2. React Native deep dive, 深入理解React Native iOS执行过程, 讲解了React.js怎么参与进来的, 对每个主要的类的角色有详细的解释。
  3. JavascriptCore Apple doc
  4. a deep dive into react-native, 作者貌似是React Native的开发成员, 讲解了一些技术选择和决策层面的东西, React.js Conf 2015 Keynote 2 - A Deep Dive into React Native
  5. Deep diving react-native debugging, medium.com, 深入讲解react-native调试的原理和流程, 对调试原理感兴趣的非常值得一看。要翻墙.
  6. Inspecting react-native bridge traffic, medium.com, 查看 react-native bridge traffic, 挺有意思的一个技巧。要翻墙
  7. Debugging react native performance ,讲MessageQueue的, 性能调试相关。要翻墙。
  8. Debugging react-native packager, medium.com, 调试react-native packager, 也就是调试一个webpack类似的东西, 很有意思.
  9. React Native’s JavaScript Execution Contexts
  10. debugging react-native app, 这个里面有提到react-native bridge 和底层native代码的调试机制. 还提到react-native slow-log, 一个time profiler库。还提到了JSEventLoopWatchdog, 还有react perf
  11. James Ide的blog
  12. How to create your own native bridge, 创建自己的bridge, 只能说老外钻研能力真强!这老哥是React Native core team的,牛逼! 里面用了ChakraCore, microsoft edge的javascript引擎, 样本项目源码在github, 还提到一个抽象语法树(AST)生成器
  13. Under the hood of React Native by Martin Konicek,
  14. Hot Reloading , Hot Reload的详细解释.
  15. Hot Module Replacement with webpack
  16. Babel preset for react native , 代码编译转换用的是babel.
  17. Babel 配置文件使用方法
  18. Hot module Replacement 代码
  19. Dan Abramov, Hot reloading in react的作者写的hot reloading
  20. Live editing with webpack
  21. Style loader
  22. 讲webpack
  23. Webpack index , Webpack Introduction webpack 官方文档.
  24. React Native通信机制 ,
  25. React Native 通信机制详解 , 这是比较老的一篇了, 还挺深的.
  26. 探究react native通信机制 , 里面提到js和native端的config映射,’A’,’B’,’C’映射。
  27. React Native internals - part 1 - MessageQueue , MessageQueue 是js和native代码的唯一接口和通道, mqt_js是js thread, mqt_native_modules是js调用native code用的线程。有卡顿演示代码.
  28. Debugging react native applications ,
  29. 官方文档,Native Js互调和回调 , 有两种回调方式,一种是普通的error回调机制,另外一种是支持Promise的机制,重点在RCTResponseSenderBlock, RCTPromiseResolveBlockRCTPromiseRejectBlock, constantsToExport, sendEventWithName, suppportEvents
  30. React Native与OC之间通信那些事儿 , 整个流程就是这样,简单概括下,差不多就是:JS函数调用转ModuleID/MethodID -> callback转CallbackID -> OC根据ID拿到方法 -> 处理参数 -> 调用OC方法 -> 回调CallbackID -> JS通过CallbackID拿到callback执行
  31. jsBridge 解析
  32. 简书 - 深入ReactNative 第一篇 通讯及消息循环代码剖析 , 非常详细, 还有示例代码,非常值得深入看看.有懒加载的解释。
  33. nshipster - JavaScriptCore , 详解JavaScriptCore的运行机制, 这应该是一切的起点。
  34. 扩展React Native Dev Menu, 改造React Native的Dev Menu.
  35. JavaScript环境
  36. Storybook独立组件编写工具
  37. React.JS internals: Virtual DOM
  38. Virtual DOM in ReactJS, 有解释为何更新RealDom会很慢.
  39. React Reconciliation, 官方对Reconcilation的讲解.
  40. Browser Internals
  41. React Components, Elements, and Instances, 用plain js解释了components.
  42. Haul - React Native developing command line tool
  43. Ignite - 模板生成工具
  44. React Native Roadmap
  45. React Native Release Notes
  46. 关于shadow view的解释
  47. RCTWebViewExecutor最后一次在0.15里面出现
  48. react和webpack
  49. webpack打包机制
  50. Dive into react native performance
  51. React native internals,比较简短