开始React
两年前我们向大家展示了React。从那开始它取得了长足发展,不管是Facebook里面还是其它公司。今天,哪怕没有人强制使用,Facebook里新的Web项目一般都是使用React。这种模式在整个业界被广泛采用。工程师采用React是因为它能够让他们聚焦于产品,而不需要花太多的时间去折腾框架本身。我们花了大量的时间使得它变得如此强大。
React强制我们将应用分解为离散的组件,每个组件表示一个视图。这些组件让我们在迭代产品的时候更加轻松,不需要每次改一个地方都需要惦记着整个系统。更重要的是React将DOM那些变化多端的API封装成声明样式的接口,增加了编程模型的抽象层次,从而简化了程序。在构建React的时候,我们发现代码变得更具有可预见性。这种可预见性使得我们能够更快的进行迭代,并且程序更加可靠。另外,React不但让程序变得容易扩展,同时团队规模的扩张也变得更简单。
在快速迭代的Web开发周期中,我们能够用React构建出更好的产品,其中包括Facebook.com上的许多组件。另外,我们在React之上用JavaScript开发了许多让人惊讶的框架,比如Relay,极大的简化了数据获取的过程。当然,Web开发只是React的一部分能力。Facebook还将它应用到了Android和iOS应用开发上。多平台开发让我们的工程机构变得分裂。这还只是原生移动应用开发的难点之一。
为什么原生开发很复杂
有许多原因使得原生移动应用开发比Web开发要复杂。在屏幕上进行布局是其中的一点,我们经常需要手动计算视图的大小和位置。并且我们不能使用React或Relay等框架来帮助我们简化开发流程和工程机构规模的变化。最痛苦的是当我们迁移到移动端时,开发速度严重变慢。
当进行Web开发时,我们只需要保存文件,并且刷新浏览器就可以看到修改结果。而原生应用开发时,每次修改代码后都要重新进行编译,哪怕只是将文字在屏幕上稍微移动一下。就这样,工程师们的开发速度变得很慢,特别是代码量较大的情况下。构建原生应用时,新功能的测试也很麻烦。在Facebook,我们一天可以部署两次新版的网站,并且立马就可以得到测试结果。而移动端,可能需要等待数周甚至几个月才能得到测试结果,因此我们很少发布新版应用。“快速行动”的理念已经深入Facebook的基因,但是在移动端我们确实无法工作得像Web端一样快速。那为什么我们不使用Web呢?
我们为这些平台单独开发原生应用,是为了给用户提供比Web端更好的体验。
为什么需要原生应用
尽管开发原生移动应用需要花费更多的时间,但还是有许多原因让我们能够提供比Web平台更好的用户体验。其中一个是我们能够访问平台相关的UI组件,比如地图、日期选择器、开关和导航栈等。虽然可以在Web中重新实现这些组件,但是怎么也做不到完全相同的体验,并且不会随平台的更新而自动变化。我们同样没法像原生应用一样在Web端实现复杂的手势。
在Web端,我们没有复杂的线程模型,因此使用多线程进行并发操作。我们可以使用Web Worker在后台执行一些应用逻辑,但是无法将类似图片解码或文本相似度计算等耗时的工作的任务放入子线程中。这也是构建高性能和高响应性Web应用的最大挑战之一。
各取所长?
我们最想做的是集合原生应用的用户体验和Web平台React的开发体验于一体。我们有一些方法可以做到这一点:
- 使用WebView
一种可能是在原生应用中封装WebView。我们在尝试这种方式几年后,确实觉得这是一个好主意。但是我们的实现并没有提供所想要的性能和扩展性。这种方法非常灵活,并且能够发挥Web开发的优势,比如充分利用React的特点以及Web的快速迭代。遗憾的是所有的渲染都是使用Web技术,因此无法达到原生开发的用户体验。
- 将React移植为原生框架
将React移植为原生代码是一个好主意,但事实上我们已经在iOS端这样做了。这个项目叫做ComponentKit,并且在昨天的F8会议上将它开源了。使用ComponentKit,我们可以享受所有React的好处,比如声明式语法,可预测的UI等。并且还可以继续拥有原生环境的优势,比如使用平台相关组件、复杂手势处理以及异步图片解码、文字渲染等。由于ComponentKit使用flexbox进行布局,我们不再需要手动计算视图的位置和大小,从而使代码变得更加简单和易于维护。
不过这种方法还是有一些小问题。首先,它只能用于iOS,因此当我们想在Android平台使用的时候,就需要重新实现一遍,并且告诉工程师如何使用。其次,我们不能使用Web平台哪些基于React构建的组件,比如帮助我们处理数据获取的Relay。更重要的是,我们还是不能改进开发速度,每次修改后还是需要重新编译。
- 原生脚本化
如果我们使用JavaScript调用原生API,就可以充分利用原生环境的性能,并且能够快速迭代和利用已有的JavaScript框架。其次,既然是使用JavaScript,我们就能够让这个技术栈跨平台。听起来这就是我们想要的,毫无疑问,已经有大量的框架在这么做了。但实际上并不是这么直接。
原生脚本化的难点
如果我们同步地在原生环境和解释环境之间进行切换。UI线程可能会被JavaScript阻塞。为了提高效率,我们希望在子线程执行JavaScript,但这样做非常复杂。第一个原因是资源。如果JavaScript代码需要访问其它线程中的资源,比如一个已经被渲染的视图的尺寸,我们需要给系统加锁。而这样做又会导致UI卡顿。第二个原因是原生环境与JavaScript虚拟机之间每次交互都有一些固定的限制。如果我们需要经常跨线程操作,就不得不一次又一次的面对这些限制。
如果很好的处理上面的问题,应用就可能变得比完全用原生代码或JavaScript更糟。因此需要从根本上改变编程模型,并且确保线程间的消息传递是异步的,还能够尽可能多的将消息打包在一帧中进行传递,从而减少线程间通信的问题。
幸运的是,React给我们提供了一个完美的编程模型。
React Native简介
由于React Native组件纯粹只是包含一些返回当前视图的外观属性,没有副作用的函数。我们在修改它们时,不需要读取已经渲染出来的视图的具体实现。React非阻塞的处理DOM,但并不与DOM紧耦合。它能够封装任意的视图系统,比如iOS的UIKit。
让React能像现在GitHub上的代码一样能够支撑真实的原生应用。与移动环境唯一的区别是,React在浏览器中运行,并且渲染成div
和span
,而React Native在应用中嵌入一个JavaScriptCore`实例,并将代码渲染成系统相关的组件。
这种方案的优点是可以在一个老的产品上逐步的应用它。如同我们不需要为了开始使用React而完全重写Facebook.com,也不需要为了使用React Native而完全重写Facebook的移动应用。
这是正确的方向
我们已经开始在Facebook内部使用React Native构建产品,虽然还有大量的工作需要做,但它确实可以很好的工作。需要注意的是,我们并不追求“一次编写,到处运行”。不同的平台有各自不同的外观、性能等,我们还是应该为每个平台单独开发应用。但是同样的工程师应该能够为不同的平台开发应用,而不需要为每个平台学习一门新的技术。我们把这称为“一次学习,到处编写”。
如果你有一个iPhone手机,就可以尝试一下App Store中用React Native编写的应用。Facebooks Groups是一个混合型应用,它包含原生代码和React Native JavaScript代码,而Facebook Ads Manager则完全使用React Native编写的。
开源
在Facebook,我们的目标是让世界变得越来越开发,联系越来越紧密。因此我们想要通过开源来为这个目标增加动力。React Native也是一样。我们意识到不只是我们遇到工程结构的问题。我们想要与其它人面临同样问题的人一起合作,更加开放地进行开发。
今天,我们很激动地将React Native for iOS在GitHub上开源。对Android的支持随后就会放出,并且我们将持续为React for web添砖加瓦。我们希望这个iOS的初试版本能够得到大家可能多的支持。需要注意的是,现在还有许多功能没有实现。欢迎大家的反馈和贡献代码。