[譯] React Native 中那些令我收穫頗豐的痛點

jerryOnlyZRJ發表於2019-02-26

通過將 React Native 投入到各種規模的企業級專案中使用後的思考

[譯] React Native 中那些令我收穫頗豐的痛點

React Native 已經存在了一段時間了。當它支援 Android 的版本(iOS 之後大約一年)釋出後,我便使用它進行專業級開發了,我決定投入時間在 RN 上進行跨平臺開發。當我發現 React Native 時,我從事 iOS 開發工作已經六年了,而且不僅僅是 Mac OS X 的開發人員。

我已經在 App Store 和 Play Store 為我的客戶開發了四個中等大小(一萬到兩萬左右行程式碼,不包括依賴項)的專案。我還在一個使用 React Native 編寫的程式碼超過五萬行(除了本機程式碼)的大型專案中參與監督和貢獻,現在已經部署上線並執行順利。我已經積累了足夠的經驗來找出 React(和 React Native)閃耀的地方,和解決它短處的方案。

注意:我知道你們中的一些人在閱讀這篇文章時會提到 Flutter。但由於它的成熟度遠不及它的競爭對手,所以我還沒有深入瞭解它。

在撰寫本文時,React Native 的當前穩定版本為 0.57,即將到達 0.58RC。

這是我的想法:

React Native 最廣為人知的特性卻不是最重要的

React Native 最廣為人知的特性是跨平臺,但這並不是它引起我注意的原因。React Native 最重要的特性是它使用 React,從而支援通用的宣告式佈局。跨平臺支援排在重要性的第二位。作為一名 iOS 開發人員,我一直在嘗試那些不那麼直觀的使用者介面設計方式的 Auto Layout 系統(指 IOS 開發使用的自動佈局系統)。

如果你的系統具有高度動態性,並且螢幕上的元素相互依賴(比如一些側邊欄和動畫),那麼使用 Apple 的 Autolayout 是管理螢幕上內容的最佳方式。但是,對於大多數 Android 和 iOS 應用程式,情況並非如此。大多數 Android 和 iOS 應用程式都會使用我們經常看到的標準元素:文字、按鈕、列表、通用檢視和影像並以最類似於 Web 的方式佈局。

在 AutoLayout 出現的同時,flex 佈局也被發明出來,併成為了螢幕上元素佈局約定俗成的標準。除了標準的 Web 使用之外,還有一些佈局系統旨在利用 FlexBox 原則進行原生開發:

這只是比較出名的一些,還有很多 UI 庫。他們有一個共同點,就是他們都使用了宣告式 UI。

宣告式 UI 的特點:

建立移動開發的宣告式 UI 是為了解決傳統佈局系統所具有的問題。你宣告瞭你的意圖,系統會根據它們生成結果。

宣告式 UI 解決的一些移動開發挑戰如下:

元件化。iOS 使用 ViewControllers 中巢狀 ViewControllers 或者 views 中巢狀 views,而 Android 使用 Fragments。兩者都具有 XML 介面宣告,並且都允許執行時例項化和編輯檢視。當涉及將它們分解為較小的模組或複用它們時,你就是在進行一個小型的重構。在宣告式 UI 中,你隨時都可以對模組或者元件進行復用。

開發人員的生產力。宣告式 UI 負責為你調整元件大小。看看這段程式碼(React Native 示例):

class TestTextLabel extends React.Component {
  render() {
    return (
      <view>
        <text>This is a small text</text>
        <text>{this}</text>
      </view>
    );
  }
}
複製程式碼

上面的程式碼渲染了一個只包含兩個文字元件的 Component。注意 this.props.sampleText,如果此變數太長(例如 10000 個字元左右的長度)會發生什麼?結果將會是元件將調整大小以適合整個文字。如果文字達到了可用空間的極限(比如說螢幕大小),那麼檢視將被剪下,使用者將無法看到整個文字,你需要一個滾動檢視。

class TestTextLabel extends React.Component {
  render() {
    return (
      <ScrollView style={{flex : 1}}>
        <Text>This is a small text</Text>
        <Text>{this}</Text>
      </ScrollView>
    );
  }
}
複製程式碼

唯一改變的是新增 <ScrollView> 元素,這將需要在 iOS 上進行更多工作。

協作 —— 配合 Git 的友好體驗。我看到的每個宣告式 UI 都能更好。

在 iOS 和 Android 上,如果你有大的單片 UI,那你就做錯了。但是,大型 XML 檔案在大多數情況下是不可避免的(請注意 iOS:XIB 實際上是 XML 檔案)。它們的變化對程式碼審查者(或你)沒有任何意義,如果你不同意之前的版本(你的更改或其他開發人員)完整保留,則發起 Pull Request 幾乎是不可能的。

使用 React 和其他宣告性 UI 庫,這些問題在很大程度上被最小化,因為佈局是實際程式碼,你可以更新、刪除、合併、對比差異以及執行所有你你平時對其他軟體執行的操作。

最關鍵的是知道“效能”究竟是什麼

你可能需要成為移動開發人員才能掌握效能的概念,並管理有效的記憶體和資料處理器使用。

Web 開發人員可以在不瞭解原生的情況下使用 React Native 開發,這種說法僅適用於小型專案。一旦應用程式開始增長並且 Redux 的 store 的計算開始對應用程式的效能造成影響時,你將需要了解原生端如何工作的,才能理解為什麼會這樣。你還需要意識到 React Native 中的 Redux 的 Store 導致的重新渲染與 DOM 中發生的重新渲染並不完全相同,尤其是應用中的原生元件。

同時,在 React Native 上重新渲染應用程式的元件會變得代價昂貴。由於 React Native 本質是使用 bridge,因此你在 render() 函式內部提供的任何指令都將從 JavascriptCore 傳遞到 Java 或者 Objective C++。原生端將獲取 render() 給出的 JSX 標籤,並將它們轉換為其原生對應部分,例如檢視、標籤和影像。如果轉換每秒進行數百次,那就需要不可忽略的 cpu 時間。

在效能方面,似乎 React Native 是更好的跨平臺解決方案之一。但是,在某些關鍵領域 React Native 仍然存在效能問題。

一個這樣的例子是大型資料集(和列表)。在存在大型列表和網格檢視的情況下,Android 和 iOS 都提供了一個出色且極其靈活的解決方案 —— 回收檢視。想象一下,當使用大型列表檢視(不論是 iOS 或是 Android)時,只渲染在任何給定時間顯示的單元格。其他單元格被標記為可重複使用,以便在即將顯示新單元格時可以重複使用它們。更改資料集時,作業系統只需更新顯示的單元格。

React Native 為大型資料集提供 VirtualizedList 及其派生的(FlatList 和 SectionList)。然而,即使這樣也有很多不足之處。存在效能開銷,尤其是在 SectionList 中渲染複雜元件並嘗試更新一百多個物件的大型資料集時。更新機制會使低端或中端移動裝置緩慢執行。

為了解決這個問題,我已經從 Redux 切換到 MobX,它為我的元件提供了更可預測的更新。此外,在大型列表的情況下,MobX 可以更新特定單元格而無需重新呈現整個列表。通常這也可以通過 Redux 實現,但是你需要重寫 componentShouldUpdate() 方法並編寫更多樣板檔案以避免不必要的重新渲染。在將其餘變數複製到新狀態時,你的 reducer 仍會執行一些不必要的工作。

寫在最後:總之要小心。如果你正在使用 React Native,要想讓你的應用程有最好的表現效果,就要對 React 和原生的最佳實踐都很熟悉。

瞭解 JS 執行時及其對你的影響非常重要。

可以通過將除錯資訊傳送到 Chrome 對 React Native 進行除錯。這意味著在裝置中執行實際程式碼的過程與你除錯程式碼的過程不同

Android 和 iOS 上的 React Native 使用 JavascriptCore 執行 Javascript。但是,除錯工具在 V8(Chrome)上執行。為了使系統有更普遍適用性,在撰寫本文時,React Native 在 iOS 上使用 Apple 的 Javascript Core,而在 Android 上,他們使用的是已經發布三年的 JS Core 來構建指令碼(因為 Android 沒有提供任何像 iOS 這樣現成的 JS 執行時,Facebook 也必須自己構建)。這就導致缺乏了很多 JS 新特性,比如 Proxy 物件只在 Android 上和 iOS 64 位上支援。因此,如果你想使用 MobX 5+,那你必須使用升級的 Javascript 執行時(繼續閱讀以瞭解如何做到這一點)。

執行時差異通常會導致錯誤只能在生產模式中重現。更糟糕的是,甚至有些錯誤會變得難以除錯。

例如,當涉及 React Native 時,移動端資料庫的最佳解決方案是 Realm。但是,當進入除錯模式時,會發生這種情況:github.com/realm/realm…。雖然 Realm 的研發人員已經解釋了為什麼會這樣,但最重要的是,如果我們想要一個更穩定的除錯解決方案,必須改進 React Native 的除錯架構。好訊息是我一直在使用 Haul 作為我的捆綁包,它允許我直接從我的 iOS 裝置進行除錯,而無需通過 Chrome Dev Tools(不幸的是,你需要 Mac、iOS 模擬器和 Safari)。

請注意,Facebook 上的人已經發現了這個問題,他們正在重新設計 React Native 的核心,以便原生和 React Native 部分可以共享相同的記憶體。完成此操作後,可能可以直接在裝置的 JavaScript 執行時上進行除錯。(參照文章:React Native Fabric (UI-Layer Re-architecture)

不僅如此,React Native 社群現在提供了 JS android 構建指令碼,它能夠構建針對較新版本的 JavascriptCore 的指令碼並將其嵌入到 React Native 應用程式中。這使 Android 上的 React Native 的 Javascript 功能能與 iOS 相提並論,也為在 Android 上執行的 React Native 增加了 64 位支援奠定了基礎。

使用 React Native 進行應用內導航效果非常棒

你是否開發過帶有身份驗證的移動應用程式?如果使用者收到一條推送通知,並且只有先在登入介面登入後才能看到推送通知內容介面,會怎麼樣?或者,如果你當前已經在一個應用程式中的深層次介面並希望跳轉到另一個應用程式中的完全不同的區域作為對使用者操作的響應,又該怎麼辦?

使用原生的方法可以解決這一問題,但需要花費一些努力。而使用 React Navigation,它們甚至都不是問題。深層的連結和導航跳轉能讓使用者感覺自然而流暢。雖然還有其他導航庫,但 React Navigation 被認為是事實上的標準。你應該試一試,這是 React Native 比 iOS 和Android 更好的地方

React Native 不是銀色子彈(靈丹妙藥)

與任何其他技術一樣,你需要在投入使用前瞭解它能做什麼不能做什麼。以下是 RN 在哪些類別的應用上具有優勢:

  • 內容驅動的應用程式
  • 具有 Web 式 UI 的應用程式
  • 可能需要或可能不需要快速上市時間的跨平臺應用程式

這裡還列出了一些對於 RN 來說表現不算太好的一些類別的應用:

  • 具有巨大列表的應用程式
  • 無需佈局的媒體驅動的應用程式(例如:簡單/小型遊戲、動畫、視訊處理程式)或螢幕到螢幕的過渡。
  • CPU 密集型任務

確實,對於 React 無法做到的事情,你可以在原生中編寫所需的所有內容,然後從 React Native 呼叫相應的程式碼。但這意味著你需要為每個平臺(iOS、Android)編寫一次程式碼,然後為 Javascript 介面編寫額外的程式碼。

React Native 的內部元件目前正在經歷一個主要的重構,以讓 RN 可以並行執行更多同步操作,以便它可以與原生共享公共程式碼:facebook.github.io/react-nativ…。因此,在此之前,你應該在決定是否使用它之前進行一些研究。

結論

React Native 是一個經過深思熟慮且發展良好的平臺。它為你的應用開啟了 NodeJS 的世界,讓你在最好的佈局系統中進行程式設計。它還為你提供了與原生方面的良好 bridge,以便你可以充分利用這兩個世界。

然而,它也屬於另一個奇怪的類別,有時候你只需要一個團隊來開發你的應用程式,但有時候也需要三個!在某些時候,你需要一些 iOS 和 Android 開發人員來構建 React Native 預設情況下沒有的元件。一旦你的團隊開始成長,你將不得不決定是否將你的應用程式設為 100% 原生。因此,你為下一個專案是否選擇 React Native,取決於你擁有多少原生程式碼(Java、Kotlin、Swift、ObjC)。

我的個人建議:如果你意識到你需要三個團隊來開發一個應用程式的三個不同層面(一個 iOS 團隊、一個 Android 團隊和一個 React 團隊),那麼你應該可以一直使用原生 iOS 和 Android 並拋棄 React Native 。僅維護兩個程式碼庫而不是開發三個程式碼庫,你將節省時間和金錢。

但是,如果你擁有一個由熟練的開發人員組成的小團隊並希望構建內容應用程式或類似的軟體,那麼 React Native 是一個很好的選擇。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章