【编者按】本文作者为 14islands 联合创始人、创新 Web 开发者 David Lindkvist,主要介绍有关混合型应用搭建的方方面面。文章系国内 ITOM 管理平台 OneAPM 编译呈现。

最近,我们有幸与 Fjord 合作,从零开始为其用户打造了一款 HMTL5 混合型应用。

混合型移动应用(Hybrid apps)可以借助多种 web 技术搭建应用,并将其打包为原生应用(Native apps)以适应于多种移动平台。

在本文中,我们将分析使用 ReactCordova 创建 iOS 与 Android 应用时采用的技术以及面临的挑战。

注意:React Native 在2015年首发。然而,在本项目开始时,React Native Android 版还未发布,因此我们无法使用之。

混合型应用中的挑战

混合式移动应用已经不是什么新鲜事了。同时,它当然也不是编写所有应用的万能钥匙。真正的挑战在于,达到原始应用的极致体验,兼具流畅的动画效果与时尚的用户界面。

过去,使用诸如 Backbone.js 这类更为传统的 JavaScript MVC 框架,我们已经在这一方向上做了多次冒险尝试与努力。

大多数混合式应用项目一开始,都具备快速、响应及时的用户界面。之后,却很容易撞上南墙。这通常出现在项目后期,此时,经过数周的努力,项目已经添加了许多的功能,DOM 中的内容也愈加丰富。

此时,视图组件间的关系变得非常难以追踪,而事件监听器的循环依赖会导致过多的 DOM 读写操作。

进入 React

React 是一个用于创建用户界面的 JavaScript 函数库,通常被表述为 MVC 中的 V(View,视图)。

React 知道根据组件的状态进行重新渲染,并且保存一个虚拟 DOM 以实现高效的重新渲染。这种方法非常棒,因为我们写代码时就好像在重新渲染整个模板,而实际上 React 只会更新发生过改动的 DOM。

JSX

React 与常见框架的最大差别在于,JavaScript 逻辑与 Markup(标记)模板使用 JSX 语法写在同一个文件中。

class MyTitle extends Component {
  render() {    return (
      <header>
        <h1>Hello World</h1>
      </header>
    )
  }
}

适应这种变化需要一点时间。但是一旦掌握,就能极大地你的提高生产力。

Mixins 对决 Composition

笔者是现代 JavaScript 的狂热粉丝,偏好使用 Babel 编写 ES2015 语法。

Mixins 不能与 ES2015 并用,原因在此。所以,我们选择 Higher-order-components(高阶组件)来创建功能特征,而非 mixins:

/**
 * Exports a higher order component wrapping the component to decorate
 * @param ComponentToDecorate the component which will be decorated
 */
 export const withDecoratedData = ComponentToDecorate =>  
 class extends Component {
    constructor() {      
      this.state = { data: null };
    }
    componentDidMount() {      
      this.setState({ data: 'Decorated hello!' });
    }
    render() {      
      return <ComponentToDecorate {...this.props}  data={this.state.data} />;
    }
  }

之后,可以使用 ES2016 装饰器(Decorator)来应用组件。我们可以在 Babel 中选择启用 ES2016 装饰器。

import {withDecoratedData} from '...';
// Decorate component using ES7 decorator '@'
@withDecoratedData
class MyComponent extends Component {
  render() {    
    return <div>{this.data}</div>;
  }
}

通过这种方式,我们将视图组件(View components)与我们的数据存储(Data stores)进行了联结。

单向数据流

对于一个应用而言,视图层只是表面——表面背后的部分才是错综复杂的境地。React 可以与大多数其他框架结合使用,实现对现有数据模型的渲染。然而,大规模 MVC 应用与循环依赖的问题仍旧存在,因此,Facebook 推出了具备“单向数据流”的 Flux 设计模型,以使数据流动更容易预见。

为什么选用 React 创建混合型移动应用? 技术分享 第1张

Flux 的实现方式不胜枚举。在研究了其中一部分案例之后,我们选定了Alt

UI 样式与动画

为了让应用尽可能地接近原生,UI 动画达到 60 帧每秒,并且没有闪烁现象是至关重要的。移动端浏览器的 JavaScript 性能一直都慢得引人注目,因此,我们确保只使用纯 CSS 动画与转换。

行内样式 对战 CSS

最近,React 世界非常热烈的一个话题是:是否使用行内样式,也即:在元素样式属性内部设置样式,而不使用 CSS。

实话实说,笔者更喜爱 CSS,对行内样式并不非常感冒。CSS 对重要内容的划分非常清晰,而作为 web 开发者,我们早已熟知如何有效地应用响应式 Web 设计原则(Responsive Web Design principles)来支持不同的设备性能与屏幕大小。

行内样式的最大争议在于:“状态”在很大程度上是 JavaScript 关心的问题。很多时候,我们需要根据动态情况来改变样式。不过,你想一下就会发现,通过添加或删除修饰符类以传播状态变动其实是很完美的方法。

BEM 钟爱 React

笔者偏好使用 Saas 与经过些微修正的 BEM 类命名惯例编写大部分样式。我们修改了 BEM 块名使其匹配 CamelCased JavaScript 类名,从而为每个组件实现明确的 JavaScript 与 CSS 组合。

class MyComponent {
  render() {
    const activeClass = this.props.active ? 'MyComponent--active' : '';    
    return (
      <div className={"MyComponent " + activeClass}>
        <h1 className="MyComponent__title">
          My title
        </h1>
      </div>
    );
  }
);

对于具备许多状态修饰符的组件而言,这会显得有些凌乱与繁琐。为此,笔者创建了自己的 bem-helper 以简化 BEM 类名在 JSX 中的使用。

import BEM from 'bem-helper-js';
class MyComponent {
  render() {    
    return (
      <div className={BEM(this).is('active', this.props.active)}>
        <h1 className={BEM(this).el('title')}>
          My title
        </h1>
      </div>
    );
  }
);

它会自动从 JavaScript 类名中获取块名,并认为 this.props.active is truetrue 时,下面的类名就会被渲染:

<div class="MyComponent MyComponent--active">  
  <h1 class="MyComponent__title">My title</h1>
 </div>

通过 React 实现动画

对习惯了手动添加类或修改样式的人而言,这部分可能会有点水土不服。现实是,我们不得不后退一步,让 React 处理 DOM 的所有更新。

大多数动画库都会直接访问 DOM,因此,请仔细选择。

幸运的是,React 团队已经为我们提供了 ReactCSSTransitionGroup,能帮解决应用动画类、在 DOM 中增减动画元素等常见场景。在我们的应用中,它有效地处理了页面转换。

收尾

我们使用了 Apache Cordova来打包应用,生成 iOS 与 Android 版本。其设置相当简单直白,并且提供了许多有用的插件,通过一个 JavaScript API 就能实现一些原生功能。

举个例子,我们包含了 Statusbar 插件,在运行时改变原生状态栏的颜色。

为什么选用 React 创建混合型移动应用? 技术分享 第2张

从 iOS 8 开始,我们终于可以在惯性滚动阶段(也即在触摸停止后持续的滚动动作)设置滚动事件。旧版 UIWebView 并不支持该功能,而 Cordova 默认使用旧版 UIWebView。

对于 iOS 9 用户期待的 WKWebView 引擎,官方提供了一个 cordova 插件。然而,如果不启用 CORS,无法通过 file:// 协议使用 XHR。

总结

对于使用 React 完成此项目,我们对自己的选择感到欣慰。但是,我们仍有一些值得注意的地方,以便在下次做出调整。

优势

  • 渲染性能的提升 —— React 能高效地实现 DOM 的更新

  • 简化可重用组件的编写

  • 强大的 JSX 语法,实现数据与标记模板的结合

  • 一旦体系决策达成,组件开始重用,生产力就能提高

  • 避免开发者直接接触 DOM (也即:减少伤害性能的风险)

缺点

  • 如果不人为直接修改 DOM,使用 React State 很难实现时间线复杂的动画

  • 并非全面的解决方案 ——缺少经验的开发者很难入门。需要选择一个 Router, Flux 库或数据层等等

  • 新的 React 版本发布较为频繁,生态系统不够成熟 ——大多数插件的变化比 React 还频繁,而且 API 一直在变化。在本项目中,我们在 react-routerAlt 中都遇到过断层式的 API 变化。Alt 的变化尤其迅速,相关文档也不是最新的。在下一个 React 项目中,我们会专注于 Redux

接下来去哪儿

现在,React Native 的势头越来越猛,因此值得进一步追踪。关键的不同在于,它在 JavaScript 与原生 SDK 之间有一个代理层。它在单独的线程中运行 JavaScript 代码,因此在执行其他操作时还能保证流畅的动画。此外,通过 Flexbox 方法,React Native 也选择了行内样式而非 CSS。据估计,iOS 与 Android 之间超过 85% 的代码库可以实现共享


本文系 OneAPM 工程师编译呈现。OneAPM Browser Insight 是一个基于真实用户的 Web 前端性能监控平台,能够帮大家定位网站性能瓶颈,网站加速效果可视化;支持浏览器、微信、App 浏览 HTML 和 HTML5 页面。想阅读更多技术文章,请访问 OneAPM 官方技术博客


原文地址:http://14islands.com/blog/2016/03/03/why-we-chose-react-for-hybrid-app/