为什么选用 React 创建混合型移动应用?
AnalisaQke
8年前
<p>【编者按】本文作者为 14islands 联合创始人、创新 Web 开发者 David Lindkvist,主要介绍有关混合型应用搭建的方方面面。文章系国内ITOM 管理平台OneAPM 编译呈现。</p> <p>最近,我们有幸与 <a href="/misc/goto?guid=4959672802354173013" rel="nofollow,noindex">Fjord</a> 合作,从零开始为其用户打造了一款 HMTL5 混合型应用。</p> <p>混合型移动应用(Hybrid apps)可以借助多种 web 技术搭建应用,并将其打包为原生应用(Native apps)以适应于多种移动平台。</p> <p>在本文中,我们将分析使用 <a href="/misc/goto?guid=4958968605084997344" rel="nofollow,noindex">React</a> 与 <a href="/misc/goto?guid=4958873995635293187" rel="nofollow,noindex">Cordova</a> 创建 iOS 与Android 应用时采用的技术以及面临的挑战。</p> <p>注意:React Native 在2015年首发。然而,在本项目开始时,React Native Android 版还未发布,因此我们无法使用之。</p> <h2>混合型应用中的挑战</h2> <p>混合式移动应用已经不是什么新鲜事了。同时,它当然也不是编写所有应用的万能钥匙。真正的挑战在于,达到原始应用的极致体验,兼具流畅的动画效果与时尚的用户界面。</p> <p>在 <a href="/misc/goto?guid=4959672802495745081" rel="nofollow,noindex">过去</a> ,使用诸如 Backbone.js 这类更为传统的JavaScript MVC 框架,我们已经在这一方向上做了 <a href="/misc/goto?guid=4959672802581433662" rel="nofollow,noindex">多次</a> 冒险尝试与努力。</p> <p>大多数混合式应用项目一开始,都具备快速、响应及时的用户界面。之后,却很容易撞上南墙。这通常出现在项目后期,此时,经过数周的努力,项目已经添加了许多的功能,DOM 中的内容也愈加丰富。</p> <p>此时,视图组件间的关系变得非常难以追踪,而事件监听器的循环依赖会导致过多的 DOM 读写操作。</p> <h2>进入 React</h2> <p><a href="/misc/goto?guid=4958968605084997344" rel="nofollow,noindex">React</a> 是一个用于创建用户界面的 JavaScript 函数库,通常被表述为 MVC 中的 V(View,视图)。</p> <p>React 知道根据组件的状态进行重新渲染,并且保存一个虚拟 DOM 以实现高效的重新渲染。这种方法非常棒,因为我们写代码时就好像在重新渲染整个模板,而实际上 React 只会更新发生过改动的 DOM。</p> <h3>JSX</h3> <p>React 与常见框架的最大差别在于,JavaScript 逻辑与 Markup(标记)模板使用 JSX 语法写在同一个文件中。</p> <pre> <code class="language-javascript">class MyTitle extends Component { render() { return ( <header> <h1>Hello World</h1> </header> ) } }</code></pre> <p>适应这种变化需要一点时间。但是一旦掌握,就能极大地你的提高生产力。</p> <h3>Mixins 对决 Composition</h3> <p>笔者是现代 JavaScript 的狂热粉丝,偏好使用 <a href="/misc/goto?guid=4958877845976897237" rel="nofollow,noindex">Babel</a> 编写 ES2015 语法。</p> <p>Mixins 不能与 ES2015 并用,原因 <a href="/misc/goto?guid=4959672802700960179" rel="nofollow,noindex">在此</a> 。所以,我们选择 Higher-order-components(高阶组件)来创建功能特征,而非 mixins:</p> <pre> <code class="language-javascript">/** * 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} />; } }</code></pre> <p>之后,可以使用 ES2016 装饰器(Decorator)来应用组件。我们可以在 Babel 中选择启用 ES2016 装饰器。</p> <pre> <code class="language-javascript">import {withDecoratedData} from '...'; // Decorate component using ES7 decorator '@' @withDecoratedData class MyComponent extends Component { render() { return <div>{this.data}</div>; } }</code></pre> <p>通过这种方式,我们将视图组件(View components)与我们的数据存储(Data stores)进行了联结。</p> <h2>单向数据流</h2> <p>对于一个应用而言,视图层只是表面——表面背后的部分才是错综复杂的境地。React 可以与大多数其他框架结合使用,实现对现有数据模型的渲染。然而,大规模 MVC 应用与循环依赖的问题仍旧存在,因此,非死book 推出了具备“单向数据流”的 Flux 设计模型,以使数据流动更容易预见。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/cd7c38af8263e3154ad2f86bdda4ec70.png"></p> <p>Flux 的实现方式不胜枚举。在研究了其中一部分案例之后,我们选定了 <a href="/misc/goto?guid=4959645786158304575" rel="nofollow,noindex">Alt</a> 。</p> <h2>UI 样式与动画</h2> <p>为了让应用尽可能地接近原生,UI 动画达到 60 帧每秒,并且没有闪烁现象是至关重要的。移动端浏览器的 JavaScript 性能一直都慢得引人注目,因此,我们确保只使用纯 CSS 动画与转换。</p> <h3>行内样式 对战 CSS</h3> <p>最近,React 世界非常热烈的一个话题是:是否使用行内样式,也即:在元素样式属性内部设置样式,而不使用 CSS。</p> <p>实话实说,笔者更喜爱 CSS,对行内样式并不非常感冒。CSS 对重要内容的划分非常清晰,而作为 web 开发者,我们早已熟知如何有效地应用响应式 Web 设计原则(Responsive Web Design principles)来支持不同的设备性能与屏幕大小。</p> <p>行内样式的最大争议在于:“状态”在很大程度上是 JavaScript 关心的问题。很多时候,我们需要根据动态情况来改变样式。不过,你想一下就会发现,通过添加或删除修饰符类以传播状态变动其实是很完美的方法。</p> <h3>BEM 钟爱 React</h3> <p>笔者偏好使用 Saas 与经过些微修正的 BEM 类命名惯例编写大部分样式。我们修改了 BEM 块名使其匹配 CamelCased JavaScript 类名,从而为每个组件实现明确的 JavaScript 与 CSS 组合。</p> <pre> <code class="language-javascript">class MyComponent { render() { const activeClass = this.props.active ? 'MyComponent--active' : ''; return ( <div className={"MyComponent " + activeClass}> <h1 className="MyComponent__title"> My title </h1> </div> ); } );</code></pre> <p>对于具备许多状态修饰符的组件而言,这会显得有些凌乱与繁琐。为此,笔者创建了自己的 <a href="/misc/goto?guid=4959672802811566107" rel="nofollow,noindex">bem-helper</a> 以简化 BEM 类名在 JSX 中的使用。</p> <pre> <code class="language-javascript">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> ); } );</code></pre> <p>它会自动从 JavaScript 类名中获取块名,并认为 this.props.active is true 为 true 时,下面的类名就会被渲染:</p> <pre> <code class="language-javascript"><div class="MyComponent MyComponent--active"> <h1 class="MyComponent__title">My title</h1> </div></code></pre> <h3>通过 React 实现动画</h3> <p>对习惯了手动添加类或修改样式的人而言,这部分可能会有点水土不服。现实是,我们不得不后退一步,让 React 处理 DOM 的所有更新。</p> <p>大多数动画库都会直接访问 DOM,因此,请仔细选择。</p> <p>幸运的是,React 团队已经为我们提供了 <a href="/misc/goto?guid=4959672802904781315" rel="nofollow,noindex">ReactCSSTransitionGroup</a> ,能帮解决应用动画类、在 DOM 中增减动画元素等常见场景。在我们的应用中,它有效地处理了页面转换。</p> <h2>收尾</h2> <p>我们使用了 <a href="/misc/goto?guid=4958873995635293187" rel="nofollow,noindex">Apache Cordova</a> 来打包应用,生成 iOS 与 Android 版本。其设置相当简单直白,并且提供了许多有用的插件,通过一个 JavaScript API 就能实现一些原生功能。</p> <p>举个例子,我们包含了 <a href="/misc/goto?guid=4959672802999758886" rel="nofollow,noindex">Statusbar 插件</a> ,在运行时改变原生状态栏的颜色。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0f4a675dc3ce791280a096e4870ae6cc.png"></p> <p>从 iOS 8 开始,我们终于可以在惯性滚动阶段(也即在触摸停止后持续的滚动动作)设置滚动事件。 旧版 UIWebView 并不支持该功能,而 Cordova 默认使用旧版 UIWebView。</p> <p>对于 iOS 9 用户期待的 WKWebView 引擎, <a href="/misc/goto?guid=4959672803082206135" rel="nofollow,noindex">官方提供了一个 cordova 插件</a> 。然而,如果不启用 CORS,无法通过 file:// 协议使用 XHR。</p> <h2>总结</h2> <p>对于使用 React 完成此项目,我们对自己的选择感到欣慰。但是,我们仍有一些值得注意的地方,以便在下次做出调整。</p> <p>优势</p> <ul> <li> <p>渲染性能的提升 —— React 能高效地实现 DOM 的更新</p> </li> <li> <p>简化可重用组件的编写</p> </li> <li> <p>强大的 JSX 语法,实现数据与标记模板的结合</p> </li> <li> <p>一旦体系决策达成,组件开始重用,生产力就能提高</p> </li> <li> <p>避免开发者直接接触 DOM (也即:减少伤害性能的风险)</p> </li> </ul> <p>缺点</p> <ul> <li> <p>如果不人为直接修改 DOM,使用 React State 很难实现时间线复杂的动画</p> </li> <li> <p>并非全面的解决方案 ——缺少经验的开发者很难入门。需要选择一个 Router, Flux 库或数据层等等</p> </li> <li> <p>新的 React 版本发布较为频繁,生态系统不够成熟 ——大多数插件的变化比 React 还频繁,而且 API 一直在变化。在本项目中,我们在 react-router 与 Alt 中都遇到过断层式的 API 变化。 Alt 的变化尤其迅速,相关文档也不是最新的。在下一个 React 项目中,我们会专注于 <a href="/misc/goto?guid=4959672803167376147" rel="nofollow,noindex">Redux</a> 。</p> </li> </ul> <h3>接下来去哪儿</h3> <p>现在,React Native 的势头越来越猛,因此值得进一步追踪。关键的不同在于,它在 JavaScript 与原生 SDK 之间有一个代理层。它在单独的线程中运行 JavaScript 代码,因此在执行其他操作时还能保证流畅的动画。此外,通过 Flexbox 方法,React Native 也选择了行内样式而非 CSS。据估计,iOS 与 Android 之间超过 <a href="/misc/goto?guid=4959672803251346021" rel="nofollow,noindex">85% 的代码库可以实现共享</a> 。</p> <p>本文系OneAPM工程师编译呈现。 <a href="http://www.oneapm.com/bi/feature.html?utm_source=Community&utm_medium=Article&utm_term=%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E7%94%A8%20React%20%E5%88%9B%E5%BB%BA%E6%B7%B7%E5%90%88%E5%9E%8B%E7%A7%BB%E5%8A%A8%E5%BA%94%E7%94%A8%EF%BC%9F&utm_content=wk509-515&utm_campaign=BiArti&from=jsltpfga" rel="nofollow,noindex">OneAPM Browser Insight</a> 是一个基于真实用户的 Web前端性能监控平台,能够帮大家定位网站性能瓶颈,网站加速效果可视化;支持浏览器、微信、App 浏览 HTML 和 HTML5 页面。想阅读更多技术文章,请访问OneAPM 官方技术博客。</p> <p>来自: <a href="/misc/goto?guid=4959672803418866737" rel="nofollow">http://blog.oneapm.com/apm-tech/689.html</a></p> <p> </p>