【编者按】本文作者为 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 设计模型,以使数据流动更容易预见。

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 插件,在运行时改变原生状态栏的颜色。

从 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 官方技术博客

本文转自 OneAPM 官方博客

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

为什么选用 React 创建混合型移动应用?的更多相关文章

  1. react创建组件的几种方式及其区别

    react创建组件有如下几种方式 ①.函数式定义的无状态组件 ②.es5原生方式React.createClass定义的组件   ③.es6形式的extends React.Component定义的组 ...

  2. react创建新项目并且修改配置文件

    react创建项目 这是我在用react搭建项目时,用到的一些东西,顺序纯属自己定义, 一.创建项目 用react 创建一个项目,这也是官方给出的 1.npm install create-react ...

  3. C#中如何在Excel工作表创建混合型图表

    在进行图表分析的时候,我们可能需要在一张图表呈现两个或多个样式的图表,以便更加清晰.直观地查看不同的数据大小和变化趋势.在这篇文章中,我将分享C#中如何在一张图表中创建不同的图表类型,其中包括如何在同 ...

  4. react创建项目很慢,最后提示fetch failed的解决方法

    $ cnpm install -g create-react-app //创建react全局变量 $ create-react-app my-app //创建一个react项目 国内使用 npm 速度 ...

  5. React创建组件的不同方式(ES5 & ES6)

    一. 首先缕清楚React.createElement.React.createClass.React.Component之间的关系 1. React.createElement(HTML eleme ...

  6. React创建组件的三种方式及其区别

    内容转载于http://www.cnblogs.com/wonyun/p/5930333.html React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归; 具体的三种方式: ...

  7. React 创建自己定义控件

    React是Facebook的内部项目,当时facebook对自己市面上全部的javascript MVC都不惬意,于是就自己创建了一个新的框架.发现还挺好用.于是就开源了,这就是React. Rea ...

  8. React创建组件的三种方式比较

    推荐文章: https://www.cnblogs.com/wonyun/p/5930333.html 创建组件的方式主要有: 1.function 方式 2.class App extends Re ...

  9. 小窥React360——用React创建360全景VR体验

    前言    混迹VR届的发烧友兼开发者们一定不要错过这款FaceBook推出的跨端VR开发框架——React360,称为360全景体验框架更为准确,因为其前身是FaceBook和Oculus2017年 ...

随机推荐

  1. 使用mysqldump备份单表数据,并使用navicat导出单表中部分字段到excel

    今天工作上遇到一个问题,客户需要将生产环境上数据库中用户表中的用户名.登录名.邮箱三个字段导出到excel中,查了一下,分两个步骤完成了任务 1. 使用mysqldump命令将生产环境的user表备份 ...

  2. python代码位置引发的错误

    觉得python对代码位置的要求简直是变态,缩进引发的错误我以前在博客里讲过了,如果不懂可以看看我以前的博客,今天又遇到了一个代码位置所引发的错误,现在给大家分享一下: 我想要打印出来一个5*5的实心 ...

  3. python中使用eval() 和 ast.literal_eval()的区别 分类: Python 2015-05-11 15:21 1216人阅读 评论(0) 收藏

    eval函数在python中做数据类型的转换还是很有用的.它的作用就是把数据还原成它本身或者是能够转化成的数据类型. 那么eval和ast.literal_val()的区别是什么呢? eval在做计算 ...

  4. hostonly、桥接和NAT的联网方式

    不多说,直接上干货! 通信设置: 1)  hostonly,换句话就是,Windows和Linux在不插网线情况下,也是可以进行通信. 这样设置的好处,有时候,万一比如在农村,没网络.那么,这是最佳方 ...

  5. js时间戳差值转日期格式

    <script> function getRemainderTime (startTime){     var s1 = new Date(startTime.replace(/-/g,  ...

  6. rails 5.2 启动警告 warning: previous definition of VERSION was here和bootsnap

    bootsnap依赖问题 You should add gem 'bootsnap' to your gemfile to install it or remove the line require ...

  7. Python中文分词 jieba

    三种分词模式与一个参数 以下代码主要来自于jieba的github,你可以在github下载该源码 import jieba seg_list = jieba.cut("我来到北京清华大学& ...

  8. Golang gRPC 和 gRPC-gateway 结合使用

    一.安装 go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway go get -u github.com/g ...

  9. 五个步骤教你理清Redis与Memcached的区别

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由Super发表于云+社区专栏 memcached和redis,作为近些年最常用的缓存服务器,相信大家对它们再熟悉不过了.前两年还在学校 ...

  10. For update带来的思考

    For update or not 起源 ​ 之所以想写这个专题,是因为最近在做一个抢占任务的实现.假设数据库很多个任务,在抢占发生之前任务的状态都是FREE.现在假设同时有一堆抢占线程开始工作,抢占 ...