React Native: 把现代web科技带给移动开发者
译注: 这是今年5月份React Native刚发布的时候,在code.facebook.com发布的一篇博客。时隔5个月,这篇文章依然值得一读,尤其是对于想了解为何Facebook要开发并发布React Native的新手、对于React Native的由来以及和其它框架的区别感兴趣的同学,都可以读一读这篇文章。
来源:code.facebook.com 原文链接 作者:Tom Occhino
如果你对React没有什么了解,你可以先去React官方网站阅读相关的内容。你也可以直接从React Native开始了解,这是我们在2015年F8展会发布的新框架。
一切从React开始
我们在两年前就公开了我们的React框架,并且它取得了令人瞩目的成长,不论是在Facebook内还是外部。今天,即使没有任何人强迫,Facebook内部的很多web项目都已经使用React构建,它也逐步被工业界广泛采用。工程师们选择每天使用React来开发,因为它使得工程师们可以把更多的时间和精力用于关注他们的产品本身,而不是应付框架带来的各种问题。随着我们的不断使用,终于,我们开始了解是什么使React如此强大。
React强迫我们把我们的应用划分成多个互不相关的组件,每个组件作为一个独立的视图。这使得我们更容易迭代我们的产品,因为我们不用在改动一小部分的时候把整个系统都装在我们脑子里。最重要的是,React包装了复杂而易变的DOM API,改为提供一个声明式的结构,使得整个程序模型变得抽象而简单。我们发现当我们使用React来构建网页时,我们的代码变得十分可预测。这种可预测性使得我们在快速的迭代产品时更多的信任已有的代码,最终我们的应用程序也变得更为可靠。更进一步,不仅仅扩大我们的应用规模变得更容易了,我们的团队规模也更容易进行调整。
我们一起对web产品经快速迭代后,我们已经可以用React去创建一些棒极了的产品,包括Facebook.com的很多组件。在此基础上,我们还构建了一些Javascript顶层的框架,例如Relay,这使得我们的数据获取流程也变得更为简单。当然,web并不是唯一的目标,Facebook也有一些被广泛使用的Android和iOS应用,是基于一些完全不相关的技术栈来构建的。不得不为每个平台去构建App使得我们的工程师团队不得不被分隔成几个部分去运作,而这还不是移动应用开发唯一的难点。
为什么原生开发如此困难
有很多关于原生开发环境和web开发比起来难的多的解释。举例来说,在屏幕上去布局一个东西就十分困难。我们往往不得不自己去计算视图的大小和位置。我们也没办法像网站开发那样用React或Relay来简化我们的开发流程或者扩大我们的工程师团队。而且对我们来说迁移到移动平台最痛苦的事情,就是它大幅的降低了我们的开发效率。
在开发Web应用时,我们只需要保存我们的文件,然后在浏览器中点击刷新,就可以看到改变的结果。而在原生开发的过程中,我们每次改动都得重新编译和构建,即使我所做的仅仅是把一个文本框在屏幕上挪动了几个像素。这使得我们的工程师在做很多工作的时候变得更加缓慢,尤其是当一个大工程的编译特别的慢的时候。为原生开发还使得我们为新功能进行测试变得十分困难。在Facebook,我们一个网站可能每天就要发布两次,这样我们几乎立刻就能获得实验反馈。而在移动设备上,我们经常要等上几个星期甚至是几个月来等待Alpha/Beta测试的结果。因为“跑的快”是Facebook的团队基因,然而在移动设备上我们实在没法和web上跑的一样快。那我们为什么一开始要放弃用web呢?
我们现在为不同平台单独开发原生APP,最重要的原因是我们能创造最佳的用户体验,并且能和这个平台的习惯保持一致。
为什么原生开发如此必要
即使开发原生APP要耗费更多的时间,但也有很多因素导致我们通过原生应用可以给用户在移动平台带来比web更好的用户体验。譬如说,我们可以使用原生平台特有的UI组件,譬如地图、日期选择器、开关,还有导航栈。虽然在web上重新实现这些组件是可能的,但我们至今为止并没有在web上取得和原生完全一样的感受,并且他们也不会随着平台的进步而自动更新。我们也很难在web上构建复杂的手势识别系统,我们甚至还没有一个适当的工具或者开发指南来帮助我们构建这样一个系统。
在web上,我们也没有一个足够完善的线程模型,所以我们很难利用多线程并行执行工作。我们可以用web的Worker机制在后台执行一部分程序逻辑,但依然无法去做一些高负荷的数值计算,譬如在主线程以外的地方去进行图片解码、文本布局等等。这可能是开发一个高性能和快速响应的web app最大的难点。
将两个世界合二为一?
我们最梦寐以求的事情,就是既能具备原生应用的用户体验,又能获得我们用React开发web应用的开发体验。要想达到这样的目标,我们有几条路可以走:
使用WebView
最简单的办法就是在原生包装的应用程序中,插入一个WebView。我们几年前就开始做这样的尝试,并且我们确实认为这个方法不错。尽管我们的实现还达不到我们想要的规模化和性能,这种方法是最灵活的,并且能沿用全部web开发的习惯,譬如React的各种好处和web的快速迭代流程。但不幸的是,因为所有的渲染都由web相关的技术来完成,我们依然无法得到一个真正原生的用户体验。
移植React的原生版本
把React移植到原生也是一个好主意。实际上,我们在iOS平台上已经这么做了,这个项目叫做ComponentKit,这也是我们昨天在F8展会上开源的项目。用ComponentKit,你可以获得React所有的好处,尤其是声明式的、可预测的UI。我们还能获得原生开发环境的各种强大优势,使用平台独有的组件和复杂的手势处理,或者异步的进行图片解码、文本布局、和渲染等等。不仅如此,因为ComponentKit用了flexbox作为布局方案,你不再需要自行去管理应用中各项视图的位置,所以你的代码最终能变得简洁而易于维护。
不过这个方案也有几处不太重要的负面影响。首先,它是iOS独有的,所以如果我们想在Android上获得一样的好处,我们只能去创造一个独立的实现,然后教会工程师怎么去用它。并且,我们也没办法同时使用我们为web顶层构建的各种工具,譬如帮助我们解决了规模性的数据获取的许多痛点的Relay。最重要的是,我们没能改善开发速度中最大的问题——我们还得每次修改之后,重新编译和构建工程。
用脚本封装原生
如果我们用JavaScript去调用原生API,我们应当能获得原生平台的所有强大之处,同时还能享受快速迭代和使用我们现有JavaScript上基础设施的好处。不仅如此,因为基于JavaScript构建,我们应当能使得这样技术栈跨平台。这听起来恰好是我们要的全部,而且毫不意外有成千上万的框架正在这么干,然而实际上问题并没有被直截了当的解决。
用脚本封装原生是一件需要技巧的事情
如果我们只是同步的在原生环境和解释环境之间调用,我们的UI线程很可能会被JavaScript执行阻塞住。要提升界面的响应效率,我们知道我们必须把JavaScript放到主线程之外执行,但这么做其实很困难。最直接的困难就是资源访问竞争。如果我们的JavaScript访问什么正好在被别的线程用的东西(譬如一个渲染的View的尺寸),系统就只能加锁来确保方案安全,而这又会导致UI线程的卡顿。还有一个问题在于每次原生和JavaScript虚拟机之间互相访问,在访问过程中都会带来极大的开销。如果我们要经常跨线程访问,我们不得不一次又一次的经历这种开销。
所以如果我们不找到一个正确的办法,我们的应用可能比我们整个用原生写或者整个用JavaScript写还要糟糕的多。我们不能只是把用户接口API又同步的封装了一遍然后就期待能获得一个流畅的、和原生APP一样的体验。我们必须要改变一些基础层次的设计来确保我们的系统传递消息永远是异步的,这样我们就可以把这些消息以一定的单位来打包发送,来尽可能减少一些跨线程交互的开销。
幸运的是,React恰好给了我们一个适当的模型来达到这个结果
引入React Native
因为React组件本来就被设计为一个纯粹的、无副作用的函数,函数返回每一个时刻当时的View状态,这样我们无需读取底层View的状态就可以为它写入新的状态。在浏览器环境里,React使用一个非阻塞的方式来维护DOM,但React最棒的地方在于它其实可以构成一个抽象的概念,而不是死死的绑定在DOM上。React可以绑定到任何必要的视图系统上,譬如iOS的UIKit等等。
这意味着我们只要很少的工作就可以用Github上已有的React来构建真正原生的移动应用。移动环境唯一的区别在于不同浏览器里的React用div和span标签来渲染,我们用一个植入的JavasScriptCore来运行JavaScript,然后渲染平台独有的组件。
这个方案带来的一个巨大好处在于我们可以逐步的进行方案替换。我们既可以在新项目中直接基于React构建,也可以在旧项目的合适部分中去尝试性的使用它。正如我们刚开始在Facebook.com中使用React也仅仅是在某些地方,我们也没必要为了React的好处而把所有Facebook的所有应用重写一遍。
这个方案真的可行
我们现在已经在Facebook的产品线上使用了React Native。尽管还有一堆工作需要进行,它真的让我们感觉不错。需要注意的是我们不再强调所谓的“一次编写,随处运行”。不同的平台应该有不同的外观、感觉、功能等等。我们仍然需要为不同的平台去做一些额外的工作。但是我们只需要同一帮工程师就可以覆盖到不同的平台,也不需要去学习各个平台自己的技能树。我们把这个方案叫做“一次学习,随处编写”。
Sample1
如果你有一个iPhone设备,你已经可以从App Store中下载到我们用React Native构建的一些APP。Facebook Groups是一个混合开发的应用,一部分由原生代码构建,一部分由React Native的JavaScript代码构建。而Facebook Ads Manager则是一个彻底用React Native构建的应用。
开源
在Facebook,我们的使命之一就是让世界变得更加开放和连接。所以我们也希望通过开源来为这个使命贡献。React Native并不例外。我们意识到工程师组织的难题并不是Facebook独有的问题,而且我们也希望尽可能的公开开发,与面临相似挑战的人进行合作。
今天,我们高兴的宣布React Native的iOS版本已经在Github公开。安卓支持也即将到来。我们也在持续努力推进React的web版本,但我们希望尽早发布这个初始的iOS版本,这样我们就可以从其它对此方案感兴趣的人那里获得反馈。记住现在在React Native中还有很多的问题或者尚未实现的部分,我们非常欢迎您的反馈建议,我们也非常期待看到您用React Native所构建出的App!