http://blog.ilibrary.me/2016/12/25/react-native-internaljavascript
------------------------------------------------------------------------------------css
术语、简写约定html
名词/缩写 | 解释 |
---|---|
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是Facebook出品的一个革新性的跨平台UI框架,跨平台不是它最大的亮点,它背后的[React]才应该是它的神奇说在,也是它革新所在。我在另一篇博客React Native系列(3) - props和state中有详细的分析。若是非要用一两句来总结它的伟大,那就是给把web开发中的无状态开发模式经过React实现了。 那些数不清的状态组合才是桌面应用和手机应用复杂的源头。java
本文目的是经过源码分析,详细解释React Native框架的内部结构及运行原理。文章会比较长,组织上会尽量由浅入深来说。适合的读者对象是对React Native开发有必定基础的开发者。node
分析的代码是基于0.50.3, 基于iOS平台的实现,包括原生的代码,js框架和打包器。从代码打包,react native初始化,js加载到运行以及错误处理。安卓平台的Java代码解读不在本文覆盖范围。react
ReactJS是一个很是具备革新性的web UI框架,很是简单易用。它的virtual dom和data driven编程模式对现有的UI编程模式是一种颠覆, 极大简化了UI应用复杂状态的管理,很是值得你们去试一下。ReactJS配上Redux, 你会发现作复杂多状态的应用竟然能够如此简单! React Native强大的缘由就在于它是基于ReactJS的。相信读这篇文章的人大部分以对ReactJS有必定的了解,这里就很少说。android
讲React Native以前,了解JavaScriptCore会有帮助,也是必要的。React Native的核心驱动力就来自于JS Engine. 你写的全部JS和JSX代码都会被JS Engine来执行, 没有JS Engine的参与,你是没法享受ReactJS给原生应用开发带来的便利的。在iOS上,默认的就是JavaScriptCore, iOS 7以后的设备都支持. iOS 不容许用本身的JS Engine. JavaScriptCore来自于WebKit, 因此,安卓上默认也是用JavaScriptCore.ios
因此,你深刻了解React Native的第一站应该是 JavaScriptCore.git
nshipster.cn有一篇文章 对JavaScriptCore的使用有一些简单的使用说明。读完这篇文章你会发现,JavaScriptCore使用起来是很是简单的,这得益于JavaScript的简洁设计。因此,本文不会花精力讲解JavaScriptCore。github
相信有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的描述和呈现分离开了。
在react native 里面,1和2是不变的,也是用html语言描述页面有哪些功能,而后stylesheet告诉浏览器引擎每一个控件应该长什么样。而且和浏览器用的是同一个引擎。
在步骤3里面UI控件再也不是浏览器内置的控件,而是react native本身实现的一套UI控件(两套,android一套,ios一套),这个切换是在MessageQueque中进行的,而且还能够发现,他们tag也是不同的。
Javascript在react native里面很是重要,
先上一副React Native 架构图,这是我在内部培训的时候画的一副图。
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。
#深刻 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两边分别编号,而后作一个映射,而后那个数字/字符串编号会作为一个查找依据来定位跨界对象。
本节介绍如下模块
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作的事情以下:
AppRegistry.runApplication
正式启动RN JS代码,从Root Component(
一个App能够有多个RCTRootView, 初始化的时候须要手动传输Bridge
作为参数,全局能够有多个RCTRootView, 可是只能有一个Bridge
.
若是你作过React Native和原生代码混编,你会发现混编就是把AppDelegate
里面那段初始化RCTRootView
的代码移动到须要混编的地方,而后把RCTRootView
作为一个普通的subview来加载到原生的view里面去,很是简单。不过这地方也要注意处理好单Bridge
实例的问题,同时,混编里面要注意RCTRootView
若是销毁过早可能会引起JS回调奔溃的问题。
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事件.
这是一个加载和初始化专用类,用于前期JS的初始化和原生代码的加载。
RCTBridgeModule
protocol的类, 供JS后期使用.若是RCTBridge是总裁, 那么RCTBatchedBridge就是副总裁。前者负责发号施令,后者负责实施落地。
这是实现远程代码加载的核心。热更新,开发环境代码加载,静态jsbundle加载都离不开这个工具。
封装了基础的JS和原生代码互掉和管理逻辑,是JS引擎切换的基础。经过不一样的RCTCOntextExecutor来适配不一样的JS Engine,让咱们的React JS能够在iOS、Android、chrome甚至是自定义的js engine里面执行。这也是为什么咱们能在chrome里面直接调试js代码的缘由。
加载和管理全部和JS有交互的原生代码。把须要和JS交互的代码按照必定的规则自动封装成JS模块。
记录全部原生代码的导出函数地址(JS里面是不能直接持有原生对象的),同时生成对应的字符串映射到该函数地址。JS调用原生函数的时候会经过message的形式调用过来。
MessageQueue
会帮忙把Method翻译成MethodID, 而后转发消息给原生代码,传递函数签名和参数给原生MessageQueue
, 最终给RCTModuleMethod解析调用最终的方法MessageQueue
会把回调对象转化成一个一次性的block id, 而后传递给RCTModuleMethod, 最终由RCTModuleMethod解析调用。基本上和方法调用同样,只不过生命周期会不同,block是动态生成的,要及时销毁,要否则会致使内存泄漏。注:
其实是不存在原生MessageQueue对象模块的,JS的MessageQueue对应到原生层就是RCTModuleData & RCTModuleMethod的组合, MessageQueue的到原生层的调用先通过RCTModuleData和RCTModuleMethod翻译成原生代码调用,而后执行.
这是核心中的核心。整个react native对浏览器内核是未作任何定制的,彻底依赖浏览器内核的标准接口在运做。它怎么实现UI的彻底定制的呢?它实际上未使用浏览器内核的任何UI绘制功能,注意是未使用UI绘制功能。它利用javascript引擎强大的DOM操做管理能力来管理全部UI节点,每次刷新前把全部节点信息更新完毕之后再给yoga作排版,而后再调用原生组件来绘制。javascript是整个系统的核心语言。
咱们能够把浏览器当作一个盒子,javascript引擎是盒子里面的总管,DOM是javascript引擎内置的,javascript和javascript引擎也是无缝连接的。react native是怎么跳出这个盒子去调用外部原生组件来绘制UI的呢?秘密就在MessageQueue。
javascript引擎对原生代码的调用都是经过一套固定的接口来实现,这套接口的主要做用就是记录原生接口的地址和对应的javascript的函数名称,而后在javascript调用该函数的时候把调用转发给原生接口
React Native的初始化从RootView开始,默认在AppDelegate.m:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
里面会有RootViewd的初始化逻辑,调试的时候能够从这里入手。
React Native的初始化分为几个步骤:
这里讨论的主要是RN相关的原生代码和用户自定义的RN模块的原生代码的加载和初始化。原生代码初始化主要分两步:
RCTModule
的原生模块,生成一个json格式的模块信息,里面包含模块名称和方法名称,而后注入到JS Engine, 由MessageQueue记录下来。原生代码在生成json模块信息的时候同时会在原生代码这边维护一个名称字典,用来把模块和方法的名称映射到原生代码的地址上去,用于JS调用原生代码的翻译。接下来咱们就一步一步详细讲解原生代码的初始化。
RN的初始化是从RCRootView开始的,全部的绘制都会在这个RootView里面进行(Alert除外).
RootView作的第一件事情就是初始化一个空的JS Engine。 这个空的JS Engine里面包含一些最基础的模块和方法(fetch, require, alert等), 没有UI绘制模块。 RN的工做就是替换这些基础的模块和方法,而后把RN的UI绘制模块加载并注入到JS Engine.
JS Engine不直接管理UI的绘制。
在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
React Native的NativeModules是有延迟加载机制的。App初始化的时候
React Native有三个重要的线程:
能够看到Shadow queue是queue
而不是thread
, 在iOS里面queue
是thread
之上的一层抽象,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.
待更新
待更新
Call Cyle
Executor Environment, 原文
Js to Java call, 出处bugly
消息循环, 出处