移动应用跨平台框架江湖将现终结者?速来参拜来自Facebook的React Native





# React Native使用初探
February 06 2015


Facebook让所有React Conf的参与人员都可以初尝React Native的源码—一个编写原生移动应用的方法。该方法运用了React.js的所有强大的功能来将其应用到原生应用。你可以通过使用其内嵌的基本元素来编写基于Javascript的组件,这些组件都是拥有着iOS和Android控件的支撑的。

首先,我知道Facebook现在还没有完全将其开源是个很挫的事情啦,但他们现在正在将该项目往开源的方向推进了。他们现在正在把跟Facebook环境相关的代码给移除掉并且正在准备建立一套接受开发人员对该开源项目进行贡献和参与的流程。我认为他们在努力往开源这个方向推进是个很好的事情,说明他们真的很在意React社区了。相信这个项目很快就能完全开源了。

其实本人并不认为开源的步骤慢点是个根本性的错误了。如果你认为是的话我将很乐意跟你进行探讨,但请允许我把该讨论放在另外一个议题里面吧。

因为如果你被该讨论打断思路的话,在这里你可能会错过我们今天对如何通过React Native编写原生移动应用的潮流已经发生改变的讨论。这种改变最大的地方就是它使用起来更像是在编写一个网路应用。

本人已经算是开发iOS应用多年的老手了,故此我有着很多原生应用开发的经验。在跟React Native勾搭上后,我只能用以下的表情来表达我的心情:



相信我们都听过各种由Javascript驱动的跨平台原生应用开发框架,比如Titanium, PhoneGap, 以及其他各种各样的允许不同层面与原生环境进行转化的项目。所有这些框架看起来都是比较挫的:无论你现在是想把一个网络应用包装在一个WebView里面,或者是想要硬套HTML&CSS这套很难用来构建移动应用的技术。对于后者,你几乎无时无刻不在直面原生对象,这在性能方面来说注定就是一个失败的尝试。React Native就不一样,它的布局方面其实是运行在一个独立的线程里面的,所以UI主线程就能如往常一样的尽可能的空闲出来进行界面动画等的呈现处理(它同时也提供了flexbox 来简化布局,这可并不是所有框架都能提供出来的)。

这里你仅仅需要浅尝就能领略到React Native所隐藏的无限潜力。它工作起来会让你感觉你是在做网络开发一样的舒畅。而事实上你确是在开发着一个真实的原生应用,事实上是根本分不出来这里面的区别的。在UI层面上,这事实上是根本没有区别的,使用到的都是像正常一样眩眼滑动的原生的UIViews。

这其实都是软件工程的功劳。同时这就完全进一步的证明了一个事实就是React.js是构建一个跨平台应用的正确方式。我可以使用该方式来像编写一个网络应用一样来编写一个原生应用。我们完全可以从今开始把DOM作为一个实现的细节,就好像UIViews一样。

我不否认我是很喜欢网络编程的,但如果我们一步步的埋头慢慢的认真检查每一个错误,我们就很容易把一些重大的问题跟忽略掉。网络编程的方式来编写应用从根本上说就是个很古怪的事情:HTML加上CSS所造成的混乱会对各种框架制造路障而不是让它们变得更好。也许React Native会最终是一个指引你回到正确的道路上面来的正确方式。我期盼着看到它是如何让网络编程方式变成一个更好的移动应用编程方式。我们不要把它想象成一个和网络编程背道而驰的东西,而要把它想象成是一个从网络编程进入到另外一个方向的原型。

到了这里你是否已经感觉眼花缭乱无所适从了?没事,我将会通过示例告诉你React Native是如何工作的!当然你可以在这里这里通过视频学到更多的这方面的知识了。

React Native使用的是iOS上的JavaScriptCore来运行Javascript(Android和其他平台将会在将来进行支持)。重要的一点是它将Javascript放在一个独立的线程上运行(其他框架如Titanium也是这样做的)。你使用Javascript来编写一个控件跟使用React.js其实没有太大的区别,除了你应该使用的是如View和Text而非div和a之外。你将会获得使用React生成UI组件的所有的优点(保守的说,这是非常牛X的!!!)。请谨记,JavaScript不仅仅是一门语言,它还是一个平台。存在着大量的优秀的”JS转换”工具供你使用。

React Native将你在另外一个线程通过Javascript编写的UI以最小数量的数据发送给UI主线程来把它桥接转换成原生的组件。比如把一个View转换成UIView。最赞的地方是你不需要担心你的UI主线程的更新来现实最新的改动;你只要声明你将需要UI基于某种状态进行桥接转换就行了,React使用的是一个独特的算法把最小量的必须的改动发送到该桥接来反映UI的变化(天地会珠海分舵注:如果你清楚增量存储的概念的话将会很容易理解这句话)。

编写原生UI从未如此的简单,况且这将不会有如播放动画卡顿等诸如此类的问题的出现,因为JS是在一个独立于UI主线程的线程中运行的。动画播放将会比吃了泻药还要顺畅!

基于React Native的一个OpenGL应用

我的第一个React Native应用其实并不是一个传统的应用:我编写的是一个波前对象模型演示应用。我个人一直对游戏开发比较感兴趣,但是我对编写原生UI却很反感。React Native刚好给了我一个使用编写网页UI的方式来编写原生UI的方案。

我保证你将会看到大量的使用原生导航和动画之类的传统应用演示。我认为通过非常简便的把React Native置身于OpenGL控件上面来完成相应的工作是一项很酷的事情。

去把React Native集成在你的项目里面其实是个很简单的事情,你只需要在你的控制器种创建一个RCTRootView,然后告诉它你的JS是摆在哪里,并把它添加到窗口上面就完成了。在我的尝试中,我是首先创建了一个OpenGL的控件,然后把RCTRootView作为一个子控件添加到该控件上面。该集成的过程是完全一个无痛人流的过程,哦,不好意思,是一个完全毫无痛苦的过程。(天地会珠海分舵注:近来被电视和路边广告轰炸的晕了,到处都是无痛人流之类的广告,看到无痛两个字就立刻神经反射的联系到无痛人流来了)

你可以按下Cmd+R来立刻刷新你的UI来现实你修改的任何更新。这里仅仅RCTRootView会进行更新,所以我可以很容易的对ui进行修改然后刷新ui来获得最近的修改,而并不需要重新加载OpenGL层。

以下是一个控件示例,一个用来列出可用文件列表并在其中一项被点击后加载一个网格的ObjList控件。这里将会用到一个和其他任何原生应用一样的原生的用来仅仅在控件内部进行滚动显示的ListView控件,但在这里进行使用确实一个极其简单的事情。

var ObjList = React.createClass({
  // a few methods clipped....

  selectModel: function(file) {
    controller.loadMesh(file);
  },

  renderRow: function(file) {
    return View(
      null,
      TouchableHighlight(
        { onPress: () => this.selectModel(file),
          underlayColor: 'rgba(0, 0, 0, .6)' },
        Text({ style: { height: 30, color: 'white' }}, file)
      )
    );
  },

  render: function() {
    var source = this.getDataSource(this.props.files);

    return ListView({
      style: { flex: 1 },
      renderRow: this.renderRow,
      dataSource: source
    });
  }
});

在我的App控件里面我有一个handlSearch方法来相应文本输入的改变。这里我对控件的状态做一些改变,一边让应用和ObjList控件接受到最新的状态改变来将最新的文件列表给显示出来。

handleSearch: function(e) {
  var text = e.nativeEvent.text;
  var files = allFiles.filter(x => x.indexOf(text.toLowerCase()) !== -1);
  this.setState({ files: files });
}

请注意ObjList控件里面的controller.loadMesh()这个调用。这其实是一个Objective-C的一个方法,这里我已经把它做了一个导出标识了,这样桥接就会找到它并使它在JS中变成可用。跟桥接进行协同工作其实是一个非常简单的事情,且将会变得越来越简单。以下就是loadMesh的实现代码:

- (void)loadMesh:(NSString *)path {
    RCT_EXPORT();

    dispatch_async(dispatch_get_main_queue(), ^{
        teapotNode_.material.diffuse = [self randomColor];
        teapotNode_.wavefrontMeshA = [REMeshCache meshNamed:path];
        [self reset];
    });
}

RCT_EXPORT()将该方法标识成使一个导出方法(事实上在其他地方实现的对该类进行实例化的工作会有点多)。这些方法将会在一个独立的线程中被调用,但是我这里需要的使在主线程对该mesh进行加载(因为这将会把数据加载到OpenGL当中),所以这里我网队列中排队了一块运行在主线程中的代码。

以下的视频将会给大家一个更完整的演示:https://www.youtube.com/embed/OPFf53fdUmQ

在声明的过程中来构建我们的UI控件并对简单的改变状态所产生的事件进行相应的能力是非常的强大的,React.js一定证明了这一点。到此为止,我们突然间就到了需要对原生应用进行同样处理的时候了。”一次学习,多平台编写“,犹如React开发人员所倡导的。同时请查看:Facebook来教我我们如何编写网站

--------------完------------------