React-Native的实现原理

React-Native:可以用JSX(JS的语法扩展) 混编js、css、html,只关心如何用 JavaScript 构造页面,它们终将被转换成原生的 JavaScript 并创建 DOM。具有以下特点:

高效:独创了 Virtual DOM 机制。Virtual DOM 是一个存在于内存中的 JavaScript 对象,它与 DOM 是一一对应的关系,也就是说只要有 Virtual DOM,我们就能渲染出 DOM。当界面发生变化时,得益于高效的 DOM Diff 算法,我们能够知道 Virtual DOM 的变化,从而高效的改动 DOM,避免了重新绘制 DOM。,我们能够知道 Virtual DOM 的变化,从而高效的改动 DOM,避免了重新绘制 DOM。
可预期:react是单向数据流,非双向数据绑定,不直接操作DOM对象,大多数时间只是对 Virtual DOM 进行编程,应用程序的状态(数据)是可以推演出视图的,具备可预期性。
声明式:组件是React的基础单元,使用React你做的仅仅是构建组件。通过封装,使得组件代码复用、测试以及关注点分离更加容易。

React-Native和 React的编程思路有些不同,React是以WebView(浏览器)为后端,操作Virtual DOM进行视图渲染的,而React-Native是以ios或者anroid原生控件为后端,但以React component 的方式Expose出来进行视图渲染的。

React-Native的原理框架图:


6039087-a23ff26b2ab6c525.png

React-Native生命周期:


6039087-1b6a7910e56a5bcf.png
getDefaultProps:在组件创建之前,会先调用 getDefaultProps(),这是全局调用一次。在组件被创建前加载。
getInitialState(),来初始化组件的状态。
componentWillMount:准备开始加载组件。在整个生命周期中只被调用一次,可以在这里做一些业务初始化操作,也可以设置组件状态。
componentDidMount:在组件第一次绘制之后调用,这个函数调用的时候,其虚拟DOM已经构建完成,你可以在这个函数开始获取其中的元素或者子组件了。需要注意的是,RN 框架是先调用子组件的componentDidMount(),然后调用父组件的函数。从这个函数开始,就可以和 JS 其他框架交互了,例如设置计时setTimeout 或发起网络请求。这个函数之后,就进入了稳定运行状态,等待事件触发。
componentWillReceiveProps:如果组件收到新的props,就会调用componentWillReceiveProps(),在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState()来更新你的组件状态,这里调用并不会触发额外的 render() 调用。
shouldComponentUpdate:当组件接收到新的属性和状态改变的话,都会触发调用shouldComponentUpdate(…),这个函数的返回值决定是否需要更新组件,如果true表示需要更新,继续走后面的更新流程。否者,则不更新,直接进入等待状态。
componentWillUpdate:如果组件状态或者属性改变,并且上面的 shouldComponentUpdate(…) 返回为true,就会开始准更新组件,并调用componentWillUpdate(),在这个回调中,可以做一些在更新界面之前要做的事情。需要特别注意的是,在这个函数里面,你就不能使用this.setState来修改状态。紧接着这个函数,就会调用 render() 来更新界面了componentDidUpdate:调用了 render() 更新完成界面之后,会调用 componentDidUpdate()来得到通知。
componentWillUnmount当组件要被从界面上移除的时候,就会调用componentWillUnmount()。在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等。

React-Native与原生的交互机制
我们都知道 JavaScript 是一种脚本语言,它不会经过编译、链接等操作,而是在运行时才动态的进行词法、语法分析,生成抽象语法树(AST)和字节码,然后由解释器负责执行或者使用 JIT 将字节码转化为机器码再执行。整个流程由 JavaScript 引擎负责完成的,IOS提供了一个叫做 JavaScript Core 的框架,这是一个 JavaScript 引擎。通过下面这段代码可以简单的感受一下 Objective-C 如何调用 JavaScript 代码的:


6039087-39fb70c02850ec05.jpg

JSContext 指的是 JavaScript 代码的运行环境,通过 evaluateScript 即可执行 JavaScript 代码并获取返回结果。JavaScript 是一种单线程的语言,它不具备自运行的能力,因此总是被动调用,Objective-C 创建了一个单独的线程,这个线程只用于执行 JavaScript 代码,而且 JavaScript 代码只会在这个线程中执行。


6039087-13814ffff21ce4eb.png

交互流程

在 React Native 中,Objective-C 和 JavaScript 的交互都是通过传递 ModuleId、MethodId 和 Arguments 进行的。Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的模块和方法。这样,无论是哪一方调用另一方的方法,实际上传递的数据只有 ModuleId、MethodId 和 Arguments 这三个元素,它们分别表示类、方法和方法参数,当 Objective-C 接收到这三个值后,就可以通过 runtime 唯一确定要调用的是哪个函数,然后调用这个函数。Objective-C 和 JavaScript 的交互总是由Objective-C发起的。Object-C与js的交互是通过各端的Bridge和ModuleConfig来进行的,实际过程可分为两个阶段:初始化阶段和方法调用阶段。
在RN(ios)项目中都会有 AppDelegate.m 这个文件,文件有如下代码:

image

用户能看到的一切内容都来源于这个 RootView,所有的初始化工作也都在这个方法内完成。在这个方法内部,在创建 RootView 之前,React Native 实际上先创建了一个 Bridge 对象。它是 Objective-C 与 JavaScript 交互的桥梁,后续的方法交互完全依赖于它,而整个初始化过程的最终目的其实也就是创建这个桥梁对象。

初始化方法的核心是 setUp 方法,而 setUp 方法的主要任务则是创建 BatchedBridge。BatchedBridge 的作用是批量读取 JavaScript 对 Objective-C 的方法调用,同时它内部持有一个 JavaScriptExecutor,顾名思义,这个对象用来执行 JavaScript 代码。创建 BatchedBridge 的关键是 start 方法,它可以分为五个步骤:
读取 JavaScript 源码

JavaScript 的代码是在 Objective-C 提供的环境下运行的,所以第一步就是把 JavaScript 加载进内存中,对于一个空的项目来说,所有的 JavaScript 代码大约占用 1.5 Mb 的内存空间。在这一步中,JSX 代码已经被转化成原生的 JavaScript 代码。

初始化模块信息

主要任务是找到所有需要暴露给 JavaScript 的类(Module)

初始化 JavaScript 代码的执行器,即 RCTJSCExecutor 对象

初始化JavaScript代码执行器,同时向 JavaScript 上下文中添加了一些 Block(Object-c中对闭包的实现) 作为全局变量。

Block--nativeRequireModuleConfig : 它在 JavaScript 注册新的模块时调用:

Block--nativeFlushQueueImmediate:一般情况下,Objective-C 会定时、主动的调用JS放到MessageQueue 中的方法,实际上(由于卡顿或某些特殊原因),JavaScript 也可以主动调用 Objective-C 的方法,目前,React Native 的逻辑是,如果消息队列中有等待 Objective-C 处理的逻辑,而且 Objective-C 超过 5ms 都没有来取走,那么 JavaScript 就会主动调用 Objective-C 的方法。

请牢牢记住这个 5ms,它告诉我们 JavaScript 与 Objective-C 的交互是存在一定开销的,不然就不会等待而是每次都立刻发起请求。其次,这个时间开销大约是毫秒级的,不会比 5ms 小太多,否则等待这么久就意义不大了。

生成模块列表并写入 JavaScript 端

让 JavaScript 获取所有模块的名字,作为一个全局变量存储

执行 JavaScript 源码

运行代码时,第三步中所添加的 Block(nativeRequireModuleConfig ) 就会被执行,从而向 JavaScript 端写入配置信息。

image

初始化工程

方法调用

OC调用 JS代码

OC不会直接调用实际的js函数,而是会去调用维系的中转函数,中转函数接收到 的参数包含了 ModuleId、MethodId 和 Arguments,就可以查找自己的模块配置表,找到真正要调用的 JavaScript 函数。

  1. JS调用OC代码

在调用 Objective-C 代码时,JavaScript 会解析出方法的 ModuleId、MethodId 和 Arguments 并放入到 MessageQueue 中,等待 Objective-C 主动拿走,或者超时后主动发送给 Objective-C。

函数内部在每一次方调用中查找模块配置表找出要调用的方法,并通过 runtime 动态的调用。

5,React Native 优缺点分析

优点

复用了 React 的思想,有利于前端开发者涉足移动端。

能够利用 JavaScript 动态更新的特性,快速迭代。

相比于原生平台,开发速度更快,相比于 Hybrid 框架,性能更好。

缺点

做不到Write once, Run everywhere,也就是说开发者依然需要为 iOS 和 Android 平台提供两套不同的代码,比如参考官方文档可以发现不少组件和API都区分了 Android 和 iOS 版本。即使是共用组件,也会有平台独享的函数。

不能做到完全屏蔽 iOS 端或 Android 的细节,前端开发者必须对原生平台有所了解。加重了学习成本。对于移动端开发者来说,完全不具备用 React Native 开发的能力。

由于 Objective-C 与 JavaScript 之间切换存在固定的时间开销,所以性能必定不及原生。比如目前的官方版本无法做到 UItableview(ListView) 的视图重用,因为滑动过程中,视图重用需要在异步线程中执行,速度太慢。这也就导致随着 Cell 数量的增加,占用的内存也线性增加。

参考资料

React Native 官方文档

React Native 官方文档中文版

React Native通信机制详解

React 入门实例教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275