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 来教我我们如何编写网站 (https://medium.com/@ericflo/facebook-just-taught-us-all-how-to-build-websites-51f1e7e996f2)
--------------完------------------
转载请尊重原创/译
作者:天地会珠海分舵
微信资源分享公众号:techgogogo 或扫描图片二维码

英文版原文:http://jlongster.com/First-Impressions-using-React-Native


↙↙↙阅读原文可查看相关链接,并与作者交流