技術深度文-使用 React Native 兩年間的心得
民宿預訂網站 Airbnb 向來是業界中相當進擊、勇於使用新技術的公司。早前 Airbnb 就他們在 2016 年開始使用 React Native (RN) 開發手機應用程式的經驗,作了個總結。
不少讀者直接跳到第五篇的結論就斷言:「早就說 RN 有這些問題啦!連 Airbnb 也跳船了!」
有人戲稱這個現象叫 「MDD」(Medium Driven Development)。
其實除了「to use or not to use」 之外,裡面也談及不少軟件工程甚至專案管理的技術。以結果論的話,未免白費了作者的一番苦心。要花點時間研讀消化一下整個系列,才能了解到 RN 是否適合你的專案。
言歸正傳,這系列技術文章共五篇,清楚交代了Airbnb 在使用 React Native 兩年間的心得:
- 第一篇:引言
- 第二篇:React Native 與預期的落差(Good & Bad)
- 第三篇:跨平台團隊在組織層面上遇到的困難
- 第四篇:Airbnb 的現在與未來
- 第五篇:如何將學到的經驗應用在原生平台上
要讀懂其中第二到三篇,需要不少的技術經驗,甚至可能要有一些大型團隊的工作經驗才能產生共鳴。這裡不會逐字翻譯,只是協助總結一下。畢竟內容太廣、範例太多了,Airbnb 這個系列的確是值得去爬原文的好分享。
第一篇:Our Experience
在十年前智能手機還未問世的時候 Airbnb 就上線了。(記得當時 Facebook 也還沒有推出 app 的日子嗎?)現在,每天有過百萬個房間預訂經由 app 完成,而為了應付這生態上的改變,現在 Airbnb 開發團隊已經成長到超過 100 名工程師了。
在 2016 年, Airbnb 面對 mobile developer 人手不足的問題,需要探尋新的出路。選擇 React Native 的其中一個原因是:本來的 Web 版就是用 React 寫成的(而且是一個很有效益、受團隊歡迎的框架)。 如果能善用 React Native,就可以令更多工程師參與開發 mobile。
當時,Airbnb 評估了使用 React Native 的一些已知風險,所以訂下了幾個目標以便日後檢討:
- 加速團隊步伐
- 維持跟原生(Native)一致的良好品質
- Mobile 的 product code 由寫兩次減省到只寫一次
- 改善工程師開發體驗 (Developer Experience)
短短兩年間,Airbnb 推出了不少以 RN 為基礎的產品,其中一個就是讓遊客分享旅遊經驗的平台 Experiences。受惠於 React Native,確實節省了開發的時間。
註:在 Oursky,我們在兩年前也開始試用 React Native 寫跨平台的應用程式。雖然我們的專案沒有 Airbnb 那麼複雜,但我們也有整理出一些簡短心得,歡迎參考看看。
第二篇:React Native at Airbnb: The Technology
第二部分主要探討技術層面,仔細記錄了 RN 的技術特點以及其優劣。不能否認的是,RN 在這兩年進化不少。
RN 是一項嶄新技術,Airbnb 獲益良多,然而伴隨而來的「痛點」也不少。
首先,是 RN 表現優異的地方(或者成功達成的項目):
1. 跨平台
基本上高達 95–100% 的 code 能夠共用,只有 0.2% 的檔案需要針對個別平台 (.android.js/.ios.js)。
2. Airbnb 開發了 DLS 這套統一的設計語言系統
為了解決跨平台的排版不一致的問題,DLS 方便建立跨平台的時候能統一設計,也能透過重寫某些組件來針對個別平台客制。例如各平台的 Toolbar 其實並不應該 100% 一樣:Android 會用到 Toolbar,iOS 上則用到 UINavigationBar,而上面的 icon 也會有所不同。
雖然方便開發和維護,但比較容易會令 native 平台和 React Native 上同一個組件產生版本上的差異。
3. React
React 本身簡單而功能強大,適用於大型 codebase。Airbnb 尤其讚賞其組件化、簡單的生命週期、宣告式設計 (declarative)的特點。
4. 加速建構流程(Iteration Speed)
用 RN 開發的其中一個好處,就是可以「hot reload」,改動了 code 只需要一兩秒就能在手機上執行結果。
用原生來開發,最少也要等個 15 秒,一個完整的程式更加接近 20 分鐘。
5. 投資基礎建設
Airbnb 為了把 RN 嵌入本來的 Native 功能中,花了不少力氣完成 bridging 的 API,例如:網絡功能、i18n、轉頁效果、手機資訊等等的元件。
雖然,要追趕新功能的 bridging 是個永恆的「兵捉賊」遊戲,但這一項投資大大減低了產品開發團隊的開發難度。
如果沒有這一層 API,使用 RN 應該是非常不愉快的開發者經驗。可以說,如果當初沒有以上沉重的投資,RN 根本不可能直接套用在一個現成的 app 上面。
6. 效能
雖然 React Native 的表現常為人詬病,但在 Airbnb 應用時的表現普遍流暢。
雖然偶有效能問題,但也能透過善用 shouldComponentUpdate, removeClippedSubviews 和 Redux 來解決。
7. Redux
Redux 一直是管理 state 的好幫手,但是它也有著相對較高的學習成本。憑良心講,這本身不算是 RN 的問題,但也足夠讓新手在開發時為之頭痛了。
8. 原生支援
因為 RN ↔ Native 中間能夠無縫橋接 (bridge),所以能夠完成很多 RN 本身不可能的任務:
- Airbnb 寫了一個叫做 SharedElement 的東西,來支援 RN 和 原生物件的過場(transition);
- 本來原生的 Lottie(用於顯示動畫的框架)也能用於 RN 上;
- Native 的網路功能 (networking stack) 和緩存(cache);
- 其他原生的核心建設,例如: i18n, Experiement 的工具等等。
9. 靜態分析(Static Analysis)
RN 配合 Airbnb 慣用的 Eslint 以外,也方便開拓使用 Prettier,減少了很多 PR 時的不必要來回。
Airbnb 也運用了不同的分析 (Analytics),去處理效能方面的問題。
RN 小巧而新穎的特性,也適合用來測試新點子。
10. 動畫效果
React Native 的 Animated 有效顯示順暢的動畫。
11. 開源生態環境
JS / React 的開源生態環境有很多不錯的 Library,例如:redux, reselect, jest 等等。
12. Flexbox
Airbnb 使用了 Facebook 的 Yoga 來處理跨平台的 flexbox layout,雖然當初欠缺了 aspect ratio 的支援,但是日益齊全的 API 已經很有幫助。
13. Web 和 mobile 共用 Redux code
Airbnb 的 web 本來也使用 Redux,因此,web 和各手機的原生平台得以共用這部分的 code。
接下來,談談 RN 的痛處:
1. 十面埋伏
React Native 發展未如 Android 或 iOS 生態那般成熟,有很多難以預測的潛藏陷阱。
2. 難以維護自己版本的 React Native fork
為了駕馭 React Native 很多的細節,需要投放大量資源自行維護一個 RN 的 fork。
過往的兩年,Airbnb 在原本的 RN 上添加了約 50 個 commits,每當要升級時都非常痛苦。
3. 痛苦的 Integration
JavaScript 作為一種無型別 (untyped) 語言,其缺乏型別安全 (type safety) 的特性成為擴大規模使用時的一大阻礙。嘗試使用 Flow ,但是由於錯誤訊息不清晰以致開發經驗欠佳;也有試過利用 TypeScript 整合到現有基礎建設上,但難以整合到 babel 和 metrol bundler。
4. 不斷的 Refactor
無型別 JS 的副作用是讓重構 (refactoring) 變得極端困難,例如在改 props 的名稱時讓人崩潰。Refactor 的部分經常在 production 才爆發 ,難以用靜態分析來避免問題。
5. JavaScriptCore 不一致
舉例說 Android 沒有提供專屬的 JavaScriptCore,React Native 內建的又太古老,迫使 Airbnb 另闢蹊徑自行打包一個 JavaScriptCore。因為使用不同版本的 JSC,所以有時候會出現意想不到的技術問題。
6. Opensource Libraries 的陷阱
React Native 的開源資料庫 (Open Source Libraries) 品質有待改進。很少人能同時精通三種平台,以至在撰寫 RN open source project 時會不一致或產生預料之外的 bug。
7. 沉重的基礎建設投資
需要大量資源從零開始構建 React Native 的基礎建設 (Infrastructure),意味著尚有大量功能有待開發。工程師需要鑽研自己不熟悉的平台,或只能等待功能被成功開發後才能繼續,兩者都會耗費額外的時間。
8. Debug 困難
使用 Bugsnag 做 crash reporting,可是因為 RN 尚新,表現可圈可點。有時候,當 crash 發生在 RN 和原生之間,因為 stack trace 並不會在兩者之間跳來跳去,除錯就變得十分困難。
9. Native Bridge
React Native 橋接原生平台的 API 過於冗長難寫,也有頗多意想不到的錯誤 (例如 integer 經常會誤傳成 string)。直至 2017 年底 Airbnb 才開始研究自動產生 TypeScript 橋接程式碼,可惜為時已晚。
10. 啟動時間過長(針對 Airbnb 應用程式的規模而言)
雖然開發時 hot-reload 很方便,而 RN 要渲染之前,要先啟動一個 runtime。由於 Airbnb 的程式有相當體積,啟動時間大概要幾秒。所以一打開 app 要用到 RN 似乎不可能。 Airbnb 要在啟動 app 的時候,同時啟動這個初始化的程序。
11. 初始 render 時間
相較於原生畫面,React Native 初始的 render 時間過長(Android 須時 440ms)。
12. App 體積變得巨大
由於要打包很多東西,Android 版本達 12MB。
13. 未能支援 64-bit
由於 RN 的這個問題,Airbnb 至今仍無法在 Android 平台支援 64-bit 版本。
14. 不一致的手勢系統
Android 及 iOS 的觸控系統差異太大,不適合使用 React Native 統一執行牽涉複雜手勢的畫面(但還是有持續研究並最新發佈了 react-native-gesture-handler 1.0 版本)。
15. 長長的列表 (Long lists)
雖然 React Native 有努力改善長列表 (long lists) 的問題,例如 FlatList library,但比起 Android 的 RecyclerView 或 iOS 的 UICollectionView,FlatList 還未發展成熟且彈性不足。
16. 升級 React Native
React Native 不健全的升級造成諸多問題(例如 RN 0.43 至 0.49 版本因為套用了測試版 React 而幾乎無法使用),甚至在 2017年中對其他 RN 的基礎建設造成巨大阻礙。
17. 輔助功能的 API 不完善
Airbnb 重視身障用戶而在 2017 年重點改善輔助使用功能。相對 React Native 的輔助使用 API 則千瘡百孔,這也是 Airbnb 需要自行維護一個 RN 的 fork 的一個原因。
18. 偶有一些非常奇特的 crash
例如這個 🤔
19. React Native 難以用到 Android 的 SavedInstanceState 機制
SavedInstanceState 是一個 Android 應用應付系統自動清理 background process 的好手法。但在 RN 裡所有的狀態都只能在 JS thread 裡存取所以無法同時進行。
第三篇:Building a Cross-Platform Mobile Team
在組織層面,使用 RN 還影響了 Airbnb 的軟件工程架構。RN 改變的並不單單只是使用一個新的 library 或 pattern 而已。
In addition to the countless technical pros and cons of React Native, we learned a ton about what React Native means for an engineering organization.
- React Native 評價毀譽參半,使用經驗也兩極化。
- React Native 快速演進、Airbnb 同時進行基礎建設和開發功能、工程師邊學邊做、跟 debug 有關的文件和指引不一致導致混亂等等變數,都讓團隊難以準確找出問題根源所在。
- React Native 無法完全擺脫原生平台的 code。事實上很少人能同時精通三種平台,以至目前無法提供高品質的 React Native 使用者經驗。
- 承上,甚少工程師能精通所有平台,要在不熟悉的平台上 debug 加上無法準確找出問題根源讓跨平台的 debug 困難重重。
- 增加招聘難度。不少人會把 Airbnb 和 React Native 聯想在一起(甚至以為 100% 使用 RN,雖然實際並不是),可能讓 Android 和 iOS 工程師對應徵 Airbnb 感到卻步。
- 寫 Hybrid App 真的很難!混合式應用程式 (hybrid app) 在分配團隊資源、協調、測試、技術處理都很考功夫。除了要 hybrid 語言之外,也要 hybrid 整個團隊和工程師。
- 要兼顧三個平台。以 Airbnb 的規模而言,要花很多時間同時維護三個穩定並保持更新的平台。
- 有時候難以在原生語言和 React Native 之間取得平衡;需要取捨的時候,工程師往往會選擇自己較為熟悉的方法。
- 測試時,很容易忽略某些平台。這源於貪方便的惰性-工程師在專長平台寫好新功能、測試一遍後,就假設其他平台都能順利運行 - 然而在 QA 甚至 production 中才發現的錯誤其實不少。
- 分工困難。當 codebase 分為 native 和 React Native 的時候,code 就會變得支離破碎,工程師可能會因為無法套用慣常的工作流程而無所適從。
- React Native 工程師容易先入為主覺得 RN 可以快速完成開發,比起使用原生語言,即使寫兩個平台只需多 50% 時間,但仍會有度日如年的感覺。
- React Native 的開放資源和參考資料比 Android 或 iOS 少很多(想當初 Airbnb 還貢獻了 CodePath 幫助大家學習 Android 跟 iOS)。相較於 native,在有限的資源下 Airbnb 需要自行投放大量資源在 RN的教育和訓練上。
第四篇:Sunsetting React Native
雖然內部團隊已經正在或打算使用 React Native,但始終難以達成本身預定的目標。基於以上種種技術和組織層面的挑戰,Airbnb 決定「向 RN 的發展就此告一段落,以及盡力返回各原生平台上」。
As a result, moving forward, we are sunsetting React Native at Airbnb and reinvesting all of our efforts back into native.
為甚麼說沒有達成目標呢?其中事與願違的事情包括:
1. 能加速整個團隊
最理想的狀態是,使用 React Native 能大幅提升開發速度。但因為上述提到的技術和行政問題,令開發充滿挫折且延誤進度。
2. 能維持跟原生一致的良好質素
隨著 React Native 生態漸趨成熟並不停累積經驗,Airbnb 略有所成(例如達成了 Shared Element Transistion、parallax 等),可惜在技術上仍然有不少難以克服的難關,而缺乏內外資源更加深了跨越難關的鴻溝,無法維持一貫的高水準。
3. Mobile 的 product code 由寫兩次減省到只寫一次
雖然 React Native 功能的 code 分佈在各平台,其實 Airbnb app 只有很小部份使用 RN,卻需要大量的基建去支援。要支援三個平台的 code,其中艱辛不足為外人道。初衷原是想共用 mobile 與 web 的 code 以及 npm packages,現實中卻很難實現。
4. 改善工程師開發體驗
本期待 React Native 能整體改善開發者經驗,但事實上 React Native 的使用經驗太極端。 Build time 的高速讓人驚豔;要 debug 時又瞬間從天堂墜入地獄。
如何從RN撤離
因為 React Native 無法協助 Airbnb 達成當初設定的特定目標,最終忍痛宣告 RN 不再適合他們。Airbnb 以「日落」(sunset) 一詞形容其過渡計劃,為 React Native 的終結添上一股美麗與哀愁。在過渡期裡,Airbnb 將停止開發 RN 的新功能,並預計在年底前優先把最高流量的畫面搬回原生語言。
到 2019 年,RN 的支援將會進一步縮減。重點是,一直支援 open source projects 的 Airbnb 將無可避免停止對 RN 開源的貢獻,而轉移部份 Airbnb 的 open source 成果到 react-native-community 將成為最後一抹日落的餘暉(現在已開始轉移 react-native-maps,未來還會有 native-navigation 和 lottie-react-native)。
然而, RN 並沒有那麼萬惡。
雖然 Airbnb 無法利用 React Native 完成他們的目標,但 Airbnb 的內部調查顯示 RN 工程師普遍對 React Native 有正面的反饋,超過六成的工程師認為使用 RN 「很美妙」(amazing),僅 5% 對 RN 抱持強烈負面的態度。63% 的工程師表示如果有機會的話還會再次選用 RN,74% 會考慮使用 RN 在新項目上。
這些數據,總結了兩年來 80,000 行的 product code,以及 40,000 行的 Javascript infrastructure,至少給予愛用 React Native 的工程師一點寬慰。
RN 已經日漸成熟
Airbnb 同時強調,它一系列的分享只描述迄今為止他們使用 RN 的經驗。然而,Facebook 和日益壯大的 RN 社群正致力讓 React Native 能擴大其在 hybrid app 的使用規模,RN 的發展速度每日俱增(去年就有 2500 個 commits)。將來 React Native 能克服技術困難的話,將大大造福用戶。
總括經驗
Airbnb 認為他們遇到的很多問題都建基於使用了 hybrid model 的做法,而 Airbnb 的規模則容許他們去接受和糾正問題(規模較小的公司未必可以這麼幸運)。Airbnb 形容要在 native 上流暢地執行 React Native 可行但充滿挑戰,而每家使用 RN 的公司都需要面對其獨有的團隊架構、產品、設計要求和 RN 發展的成熟度而衍生的特定問題。
Airbnb 特別強調 React Native 在某些功能上的表現其實超越了目標和期待(例如 iteration speed、品質或開發者經驗),甚至曾覺得改變了行動開發的遊戲規則。儘管 RN 的成就令人鼓舞,但權衡利弊後,Airbnb 認為 React Native 未能配合他們現階段工程組織的需求和資源,而不再適合他們使用。
Airbnb 似乎也知道停用 React Native 可能會造成大家對 RN 的信心危機,所以亦再次強調使用新平台是一個重大的決策,每個團隊都有自己獨特的考量,而 Airbnb 的經驗和停用理由未必適用於其他團隊。甲之蜜糖乙之砒霜,事實上 RN 在很多公司仍然行之有效。
第五篇:What’s Next for Mobile at Airbnb
雖然投資不少來實驗 React Native,但 Airbnb 其實也一直維持在原生平台發展。儘管 Airbnb 選擇不再繼續使用 RN ,從這段一起寫過 React Native 的日子還是獲得了很多寶貴經驗,也為現在的架構帶來正面的影響。
伺服器驅動的渲染 (Server-Driven Rendering)
儘管 Airbnb 不再使用 React Native,但對 RN 只需要寫一次 code 的優點是給予肯定的,所以未來仍然會大量使用 DLS 來設計。
Airbnb 已開始嘗試統一幾個伺服器驅動的 rendering framework:伺服器會傳送描述如何 render、畫面設定、將要執行的動作等到手機上,而每個手機平台會利用 DLS 組件 render 原生畫面甚至整個流程。
伺服器驅動的 rendering 漸趨複雜,也衍生一些待解決的難題:
- 如何處理更新組件時向後兼容的問題?
- 兩個平台的型別定義(type definitions)該如何處理?
- 如何處理對使用者輸入或按鍵等動作的反應?
- 兩個 JSON-driven 的畫面在轉頁時,該如何保存裡面的內部狀態 (internal state)?
- 如果渲染一個未有現成部分的全新組件時,該如何描述?Airbnb 正在實驗用 Lona 處理這個問題。
雖然在擴大規模使用時遇到以上的挑戰,但 server-driven rendering 對 OTA 測試和更新功能有很大的幫助。
Epoxy 元件
2016 年 Airbnb 把 Android 的 Epoxy 開源化。Epoxy 幫助工程師簡易地建構複雜的 RecyclerViews、UICollectionViews 和 UITableViews 等等。Epoxy 能把整個畫面分成不同組件,我們就可以 lazy-render 每個部份了。而現在 Epoxy 已同時支援 Android 和 iOS,並廣泛應用在大部份的新畫面上。
利用 Epoxy Diffing 改善效能
在 React 中,render() 負責返回一堆要顯示的元件,Airbnb 把這個概念套用到 Epoxy,讓 Epoxy 也能模仿 React 只有在資料變更的時候才重新 render 元件。配合 DSL 使用尤其有效,當有資料變更時,呼叫 requestModelBuild() 就能更新畫面。
全新的 Andriod Framework (MvRx)
可以說是揉合了React Native 的精髓,Airbnb 正在開發一個全新的 framework (內部稱之為「MvRx」),旨在結合 Epoxy、Jetpack、RxJava 和 Kotlin 的優點,並套用 React 的理念和原則,讓工程師簡單而流暢地建立各種畫面。Airbnb 大力讚許 MvRx 濃縮了 React 最精華的部份,讓滾軸和動畫變得前所未有的順暢。如果開發順利,Airbnb 更準備把 MvRx 變成開源項目。
Iteration Speed
從 React Native 回到 Native,其中一個顯而易見的轉變就是 iteration speed(大概是從光纖回到 56K 的感覺)。一個 app 要等 15 分鐘才 build 好,實在讓人很難接受。
Airbnb 實現了在 Android 和 iOS 上只重新 compile 需要的部分。
在 Android 上,就用到了 gradle product flavors。Airbnb 的 gradle module 大概長這樣:
配合 IntelliJ module unloading 使用,有效加快了在 MacBook Pro 上的 build app 速度(大概 2.5 倍)。
至於在 iOS 上的 module 則長這樣:
結果讓 build 所需的時間減少 3 到 8 倍。
結語
Airbnb 看來經歷了這兩年刺激的實驗,還會繼續保持「進擊」-不斷嘗試新的技術。 雖然 Airbnb 決定告別 React Native,但是並不代表這就解決了所有技術、組織、以及方向性的問題。
「淡淡交會過 各不留下印
但是經歷過 最溫柔共震」
在這兩年的短聚中,RN 似乎帶來不少新的衝擊,也讓「原生」的開發方式改變不少。
跨平台 app 一直以來都是軟件界的難題之一,Eproxy 和 server-driven rendering 為原生 app 的開發流程提供了更好的工具;另一方面,Google 也推行了針對 hot-reload 和運行效能的 Flutter 新框架。
Open-source 社群正努力不懈改善開發者經驗,讓開發者能夠更輕易的製作出更完善的產品給用戶使用。
順帶一提:Oursky 也推出了 Skygear 這套 open source 的工具,不論你是 Native 或是 RN 的開發者,也能方便快捷地建構 app 的 cloud backend。
本篇撰文、翻譯:David Ng、Queenie So
Oursky 致力幫助品牌與企業家實現他們的點子。如果你正在尋找合作夥伴一起建立下一個自家數碼產品,來跟我們聊聊吧!