React Native: 回顧 Udacity 移動工程團隊的使用歷程

咚咚同志發表於2018-07-08

React Native: 回顧 Udacity 移動工程團隊的使用歷程

React Native: 回顧 Udacity 移動工程團隊的使用歷程

Udacity 的移動團隊最近把我們的應用程式中用 React Native 編寫的最後一個功能移除了。

我們收到了很多關於 React Native 及其使用的一些問題,還有人問我們為什麼停止使用 React Native。

在本文中,我希望能夠回答這些問題中的大部分,並且重點介紹:

  • 我們的相關團隊的規模及人員組成?
  • 我們為什麼一開始會嘗試 React Native?
  • 移除 React Native 的原因?
  • 哪些方面可行?哪些方面不可行?
  • ...以及其它 ?

我必然不能自稱是 React Native 專家。我們團隊中有的人比我更有經驗,不過他們也未必能夠稱為專家。

我只是想談一談我們自己的經驗,介紹一下在我們這個特定的使用情況下哪些可行哪些不可行。React Native 是否適合於你的團隊/專案,這完全取決於你。而本文的作用是為你決策時提供一些額外的有用參考。

“是否在你的團隊/專案中使用 React Native 完全取決於你”

還需要指出的是,這裡的經驗和觀點來自於 Udacity 的移動工程團隊,並不涉及到公司其他團隊。我們的想法不代表其它使用 React/React Native,或為之構建內容的團隊的觀點。


團隊

第一件事,我們的團隊是什麼樣的?你的團隊的規模,經驗和組織都會對 React Native 在你的工程中的可行性產生重大影響。

我們的移動工程團隊覆蓋了 iOS 和 Android 兩個平臺。

團隊規模

引入 React Native 時

  • 1 個 iOS 開發人員
  • 2 個 Android 開發人員
  • 1 個 PM
  • 1 個 設計師

如今

  • 4 個 iOS 開發人員
  • 3 個 Android 開發人員
  • 1 個 PM
  • 1 個 設計師

在我們使用 React Native 的約 18 個月的時間裡,我們 iOS 和 Android 兩個團隊都擴容了。整個團隊還迎來了新的 PM。我們還經歷了多種設計和多種正規化。

開發者背景

當引入 React Native 時,每個團隊對 JavaScript 和 React 程式設計正規化的舒適度如何?

iOS

iOS 團隊最初唯一的那個開發者對跳進 React Native 陣營相當認同,因為他之間已經有了大量的 JavaScript 和 web 開發經驗。

現在,4 個 iOS 開發者中有 3 人在 JavaScript 和 React Native 開發中比較得心應手。

Android

引入 React Native 之初,兩個 Android 開發者之一對 JavaScript 比較適應。另一個(我自己)基本沒有什麼 JavaScript,React 或 web 開發背景。

後來加入的另一個 Android 開發者也沒什麼 JavaScript 或 web 開發經驗。


應用程式

我們的應用程式是幹什麼用的?

我們的移動應用程式的目標是將 Udacity 的學習體驗帶到移動裝置上。它們需要支援認證、內容發現、專案註冊(某些情況下還會有支付)、最後還要支援對不同的專案和內容型別的學習材料的使用。

這些應用程式還是新的實驗性功能的測試溫床,皆在改進使用者的整體學習進展。

程式碼庫的規模

  • iOS: 97400 行程式碼(.swift,.h,.m)
  • Android:93000 行程式碼(xml, java, kotlin, gradle)

功能的一致性

當引入 React Native 時,這些應用程式的功能基本上是一致的。

隨著專案推進,核心體驗基本上保持一致,但每個團隊都有一些在特定平臺上獨有的“實驗性”功能。

此外,由於更大的國際化需求,諸如本地化和更小的 apk 軟體包大小之類的需求日益成為 Android 團隊的優先事項。Android 團隊還和其它地區的團隊密切合作,為特定市場開發一些功能,這是 iOS 團隊不需要重點考慮的。


為什麼/如何採用 React Native?

我們為什麼要引入 React Native?

當時,我們想要開發一個全新的只在移動裝置上執行的功能。我們希望在兩個平臺上實驗和快速地驗證,所以跨平臺是非常吸引人的。

因為這是一個新的封閉性的功能,我們將其視為一個嘗試跨平臺方法的有意思的嘗試。

我們選擇 React Native 的原因如下:

  • 越來越可行的跨平臺方案
  • 團隊大部分人(2/3開發者)適應 JavaScript 和 web 開發
  • 更快的開發速度
  • 公司外其它團隊的成功故事

我們是如何引入的?

我們最初的 React Native 功能基於一個獨立的 GitHub 倉庫,然後將其作為 git 子樹,同時合併回 iOS 和 Android 倉庫中。

這種方式支援非常快速的原型開發,如果有必要,也可以讓這個功能以獨立產品的形式釋出。

隨著積累了更多的原型開發經驗,我們逐漸在 React Native 程式碼倉庫中加入更大的第二個功能。

時間線

  • Aug 2016: 建立功能 1 的 React Native 倉庫
  • Nov 2016: 功能 1 在 Android 上釋出
  • Nov 2016: 功能 2 開始開發
  • Dec 2016: 功能 3 原型開發開始
  • Jan 2017: 功能 1 開發結束
  • Feb 2017: 功能 2 釋出
  • Mar 2017: 功能 3 原型開發結束
  • Nov 2017: 功能 2 在 Android 上的最後一次更新
  • Dec 2017: 功能 4 作為獨立應用進行原型開發。最後由於效能原因重新用原生程式碼實現。
  • Feb 2018: 功能 2 在 iOS 上的最後一次更新
  • Apr 2018: 功能 1 從 Android 上移除
  • Jun 2018: 功能 2 同時從兩個應用中移除

移除 React Native 的動機?

答案相當直接。

我們移除應用程式中與 React Native 相關的最後一點程式碼,原因在於剩下的唯一一個 React Native 功能已經快要下線,我們不打算對它繼續支援了。

“為什麼停止用 React Native 開發新功能呢”

更有意思的問題可能是:為什麼停止用 React Native 開發新功能呢?

原因大概有以下幾條:

  1. 需要同時在兩個平臺上開發的功能數目下降
  2. Android 平臺獨有的產品請求量增加
  3. 長期維護成本的噩夢
  4. Android 團隊不願繼續用 React Native

那麼用什麼來替代呢?

用 React Native 開發的功能被移除且不再支援,我們也沒有替代這些功能的需求。


React Native 哪些方面用得不錯?

我們在使用 React Native 時,有哪些方面用得不錯呢?

  • React Native 的入門、執行、及在兩個平臺上的構建都相當容易
  • 可以從 React 和 JavaScript 更大的生態系統中獲取程式碼庫和工具
  • 我們可以在兩個平臺上同時原型開發功能 1
  • 在一個跨功能團隊中,同一個開發者可以同時為兩個平臺構建功能 2 的大部分程式碼
  • 團隊對 React Native 的共同理解更深入了

遇到過哪些問題?

在我們使用 React Native 的過程中,我們遇到了一系列的問題。其中一些來源於我們的使用過程,一些來源於我們的使用案例,還有一些則來自於 React Native 本身。

設計和使用者體驗方面的挑戰

平臺一致的 UI/UX

因為我們要將幾個新的螢幕整合到現有的更大的使用者體驗區中,我們希望新的 React Native 程式碼能夠遵守本地平臺的開發模式和風格。這意味著我們在兩個平臺上用的不一定是同一個 UI 設計。

讓 React Native 的風格和本地平臺一致並不是什麼難事,但是你需要了解每個程式碼庫中的設計正規化。至少,你要核對每個平臺,甚至要為每個作業系統開發新的定製控制元件。

對我們而言,經常需要接觸每個平臺的開發者和設計者,以理解需要做什麼,否則在兩邊同時使用一個風格會導致 Android 這邊的使用者體驗與其餘應用完全不同。

在更復雜的情況下,我們需要編寫額外的平臺獨有的程式碼,來定製每個應用程式的使用者體驗。

其中一個例子是,確保一個 back/up 圖示的正確行為。因為要考慮到新的 React Native 功能程式碼整合到已有的應用中(整合到哪裡/如何整合),確保 back/up 圖示和 back 按鈕點選時的正確行為需要 Android 平臺的原生程式碼,而且要在 React Native 程式碼庫中進行 Android 特有的程式碼改動。

在本地設計中的改動會導致整合功能時 React Native 程式碼的變動

至少在一個場合中,Android 應用的導航結構變動時,我們需要相應地更新 React Native 程式碼,不為別的,只是為了處理兩者的整合問題。

我們沒能做到讓它們獨立存在,React Native 實現的功能實際上在一個 fragment 中,它先是被放在一個 BottomNavigationView 的螢幕中,然後又被移到它自己與本地其它 fragment 之間的座標狀態中。

這種平臺修改需要回到獨立的程式碼庫,進行修改並更新整合,確保新的改動不會對 iOS 應用程式產生負面影響。

裝置相關的問題

不管你叫它“碎片化”還是“分散化”,我們面臨的現實是,Android 裝置上有更多獨特的配置需要考慮。

在很多情況下,我們發現佈局不能很好地適配到不同尺寸的 Android 手機。我們發現最新的 iPhone 和 Pixel 裝置上可以很流暢地執行動畫,但是在國際市場上更廣泛使用的低端裝置上是做不到的。

這當然不是 React Native 獨有的問題,只不過是 Android 應用開發中共有的問題。但是,這確實是我們需要考慮的平臺相關的問題,當這類問題多起來之後,我們不得不反思:React Native 的跨平臺到底為我們省了多少時間?

全球化增長

在我們使用 React Native 的時間裡,Android 團隊對國際化越來越關注。我們有數個國際化辦公室,它們要求本地化和輕量化的 apk 包。

React Native 是可以做字串的本地化的,但需要額外的設定。在我們的案例中,它需要對不同的倉庫的程式碼進行改動。這增加了本地化任務的複雜度,特別是你需要其它團隊輔助完成本地化時,就不是很美妙了。這使得 React Native 實現的功能的本地化頻率變得更低。

我們能夠按要求減小 apk 檔案大小,但是難以處理 React Native 佔用的相當可觀的那部分。當我們移除了最後一個用 React Native 實現的功能之後,我們的 apk 減少了約 10MB,這包括了命令資源(command resource)和 React Native 本身。

整合的挑戰

與本地元件和導航結構的整合

根據我們的經驗,將 React Native 整合到已有的應用中時,如果是一個獨立的功能,則整合是相當直接的;不過如何需要與已有元件緊密整合並通訊,則你會遇到麻煩。

我們發現,常常需要大量的橋接程式碼,來實現本地元件和 React Native 元件的通訊。當我們需要修改 React Native 元件以適應導航結構時,這部分程式碼也是需要更新的。

工具鏈/構建問題

整合 React Native 需要更新每個應用程式的構建過程。我們使用 CircleCI 來構建我們的工程,它需要重新配置以支援額外的 React Native 構建步驟。

在我們之前的一篇文章中介紹過,在 Android 這邊並不是那麼直觀。

在 Android 上釋出版本時的 React Native 打包: 在 Android 釋出build_engineering.udacity.com 時如何執行 React Native 打包命令列

一旦我們的構建過程包含了所需的 React Native 任務,它讓 CircleCI 的發行版構建時間增加了約 20%。

在我們的程式碼庫中移除最後一個 React Native 功能之後,我們有了如下的改進:

  • CircleCI 構建時間由約 15 分鐘變成了 約 12 分鐘
  • apk 釋出檔案大小由 28.4MB 變成了 18.1MB

Android 團隊還經歷了 Android/Gradle 構建工具與 React Native 衝突的一些問題。最近,我們一直在解決的一個問題是:issues with Gradle 4

iOS 團隊同樣存在不少問題。

配置構建工具比較痛苦,因為我們使用 React Native 時採用的是非標準檔案結構。因為我們有多個專案倉庫,我們將 React Native 倉庫置於 srcroot/ReactNative 目錄下,而很多已有的構建工具卻假設預設的應用檔案結構為 /ReactNative/ios/ios。

此外,我們使用過 cocoapods 來管理依賴關係,它原來是包含 React Native 的推薦方式,但是後來卻不再被推薦了。這個問題因為我們的非標準檔案結構而變得更為嚴重,於是我們需要在 Podfile 中用一些很惱人的小技巧來從正確的位置讀取檔案。

由於 cocoapods 不再是包含 React Native 的標準方式,於是 Podfile 的更新會依賴於社群的更新,兩者不總是同步的。我們數個版本的 css/Yoga 依賴已經更新,其 Podfile 卻仍然在引用錯誤的版本。直到最後,我們仍然在使用一些噁心的安裝後小技巧,實際上只不過是用 sed/regex 來定位那些包含的呼叫程式碼。

最後,iOS 專案的 CI 同樣是一個痛點。現在,我們不得不新增一個 npm 依賴層,並確保在繼續安裝前正確地更新。這為我們的構建時間增加了不少時間。

我們還遇到過一個崩潰問題,因為一個版本的 npm 有 package.lock,而另一個沒有,這導致我們在一次 React Native 升級時安裝了錯誤的依賴版本。


React Native 自身的挑戰

文件

React Native 作為一個整體發展是非常快的,然而我們也發現它的文件有時卻是缺乏的。特別是當我們第一次採用時,我們發現某些特定版本的文件/回答也許僅僅是相關的,也許不相關。

當前,將 React Native 整合到一個已有的工程中的文件是比較少的。這也是我們更新 CI 構建時頭痛的一個問題。

當 React Native 持續進化時,文件和社群支援已經有所改善。如果我們是從今天開始使用,也許我們曾經的一些問題更容易找到答案。

導航

我們最初是從 NavigationExperimental 開始的,它不是並容易使用的導航程式碼庫。當 React Navigation 出現之後,它迅速成為社群接受的導航,於是 NavigationExperimental 在 ReactNavigation 完全採納之前逐步不再推薦使用。

儘管如此,如果不將一些東西強行組合起來,我們是無法用 ReactNavigation 來實現一些功能的(比如:在一個當前 modal 流中推送 flow)。

效能

前面已經提過,我們有幾次都注意到了效能的問題。

我們可以在空間足夠的 iOS 和 Android 裝置上實現非常漂亮的動畫,但是這些動畫在國際市場上更廣泛使用的低端 Android 裝置上卻難以良好執行。

在進入到應用的 React Native 部分時的載入時間比我們預期要長。這使得它不像是一種無縫過渡。

當原型開發獨立的功能 4 時,圖形渲染效能成為我們關心的主要問題,因為 React Native 完全比不了本地程式的效能。

滯後於本地平臺

因為和 iOS 或 Android 本地應用並不是同步構建,React Native 常常滯後於本地平臺。它常常依賴於社群所支援的新的本地功能。

一個例子是我們急需的對 iPhone X 的 safe area 支援。我們最終不得不暫時不支援 SafeArea,因為這個功能很快就會出來。SafeAreaView 是一種平臺相關的功能,跨平臺開發者在開發相關的功能時需要考慮到。

在其它時候,React Native 在採納新平臺要求方面也是滯後的,比如在 2018 年 8 月 Android 應用 要求採用 API 26。要實現這個要求,仍然有一些未解決的問題。

不持續的更新

React Native 升級時不支援後向相容,這點讓人非常崩潰。一個例子是 React Native 在升級它的 React 庫時的 PropType deprecation

除非維護自己定製的複製倉庫,許多第三方庫如果沒有繼續維護則將無法使用。


維護的挑戰

程式碼庫中 React Native 部分的維護是我們經常要頭痛的問題。如前所述,Android 經常需要額外的工作來與已有程式碼整合或修正 UI 問題。這使得 iOS 和 Android 在不同的 React Native 程式碼庫分支上不再同步,只有這樣才不會讓不同平臺的開發互相拖累。

因為這種分支問題,程式碼開始慢慢變得發散,將它們重新一致起來所需的精力越來越多。於是,一個平臺上的更新不會立刻反映到另一個平臺上。

React Native 的改動的速度也構成挑戰。因為存在不持續的改動的可能性,我們往往不能很快地更新一個依賴關係,來增加一個新功能或修復一個 BUG。

同樣,一些時候這會產生更多摩擦,從而拖慢程式碼維護的頻率。對於一個小團隊,在有限的頻寬下,如果無法對 React Native 程式碼進行容易/快速的修復,那麼程式碼中的問題得到修復的可能性只會越來越低,因為它可能牽扯額外的開發精力。

增加了 React Native 之後,我們往往不是很清楚一個 BUG 存在於哪個層次。到底是兩個平臺都有?還是一個平臺獨有?如果只存在於一個平臺,那這個 BUG 來自於原生程式碼還是 React Native 程式碼?這些問題增加了複雜性,從而拖慢了 QA 程式。

當需要在程式碼庫的 React Native 部分中修復一個問題時,目前我們不得不同時考慮 iOS 和 Android,於是有可能需要同時考慮 3 個技術棧,而不是理想中的 1 個。

另外,因為團隊中不是所有人都對 React Native 感到適應,能夠在技術棧之間快速跳轉並修復問題的人自然就更少了。


我們是否可以做得更好?

我相信我們面臨的其中一些問題來源於我們的特定使用案例,不過,有些問題確實是可以緩解的。

更少的程式碼分支

讓應用程式與 React Native 倉庫中的改動保持一致,我們就可以做得更好。我相信讓這些更新同步會讓我們在實現這些功能時有更強烈的跨平臺開發的感覺。

增加裝置上的測試,特別是 Android,可有助於我們在早期發現更多 UI/效能問題,並且在釋出之前修復。在新程式碼編寫之前修復問題,又可以進一步減少程式碼分支的數目。

更一致的設計

從開始就採用更具體的設計計劃,有可能可以改善這些功能的本地外觀。一個具體的例子是,採用與本地應用程式其餘部分一致的 text/margin 值,而非在新使用者體驗區中使用新值,並同時應用到兩個平臺上。

更好地理解你的團隊

團隊中對 React Native 更不適應的那些成員應該更努力地適應其它技術棧。這會增加能夠快速修復問題的人員的數目。


有沒有使用案例更適合使用 React Native?

我不認為我們團隊有人認為 React Native 一無是處。顯然,我相信有些使用案例中 React Native 會更適用。

你是否需要快速地、雙平臺、從頭原型開發/構建一個新應用?

你是否需要實現一個外觀/行為與平臺無關的應用/功能?

你是否有空閒的 JavaScript 開發者用於移動裝置應用開發?

如果上述這些問題的答案都是肯定的,那麼對你來說 React Native 可能是一個可行選項。

特別是,如果你有 JavaScript/React 背景,並且不需要太多原生程式碼,那麼 React Native 將非常具有吸引力。它能讓你無需學習兩個技術棧的情況下開始構建一個移動應用。

對於一個完全跨平臺應用的重頭開發,React Native 也將是一個不錯的選擇。


我們還會不會用 React Native?

iOS 團隊和 Android 團隊有不同的說法。

iOS

有可能。iOS 團隊大體上對 React Native 開發相當開心,並且已經考慮用它來構建新功能。此外,在產品釋出方面,相對於 Android 平臺,我們的 PM 對 iOS 裝置上的 React Native 方案更有信心。

Android

不會。理想情況下,Android 團隊將不會投入到 React Native 中去。我們發現 React Native 元件的整合過程相當煩瑣,並且其使用者體驗並不能在所有 Android 裝置上都同樣執行良好。

此外,我們傾向於堅持使用一種技術棧,而不願意在 Android 框架上再增加一層抽象,因為這也意味著增加潛在的 BUG。

我們的印象是,用 React Native 為 Android 建立新功能的過程是比較快的,但是這個功能從早期階段過渡到最終釋出版本,以及長期的維護過程,往往會花費更多時間。


我們是否會再次使用其它跨平臺的方案?

作為一個團隊,我們很可能不會在短期內投入到跨平臺的開發中。iOS 團隊有可能會用 React Native 來構建一些新東西,並且這隻限於 iOS 裝置,因為他們大體上會更享受這種開發體驗。

個體而言,團隊成員會繼續關注 React Native,以及 Flutter。因為諸如 React Native 和 Flutter 這樣的解決方案也在一直進化,我們會持續為我們的團隊評估這些技術。

那麼,這就是我們今天所處的狀態。

我們對 React Native 如何能更好地適應我們的團隊以及路線圖方面有了更好的理解。我們可以基於這種判斷來指導我們下一步的決策,從而為團隊做出正確的技術選擇。

“我們可不可以確定地判斷 React Native 是否適合於你?不能。”

我們知道 React Native 的優點以及不足,那麼我們可不可以確定地判斷 React Native 是否適合你?

不能。

但是,我們的經驗完全能夠可以作為你的參考依據,幫助你評估 React Native 在你的專案中的可行性。


想要學習更多移動開發?

關注我們

想了解更多構建 Udacity 的工程師和科學家,請在 Medium 上關注我們。

想加入我們?請 @udacity,機會多多。

移動裝置上的 Udacity

[移動裝置上的 Udacity | iPad, iPhone 和 Android:我們已經將 Udacity 的課程體驗帶到了 iPad 和 iPhone 和 Android 上。開始學習你所需的技能吧)(https://www.udacity.com/mobile)

感謝 Aashish BansalJustin Li

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


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

相關文章