為什麼選用 React 建立混合型移動應用?

OneAPM官方技術部落格發表於2016-05-10

【編者按】本文作者為 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 建立混合型移動應用?

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 建立混合型移動應用?

從 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/

相關文章