Slack使用React重寫Web客戶端

jopen 7年前發布 | 27K 次閱讀 Slack

Slack使用React重寫Web客戶端

Slack 使用 React 重寫了 Web 客戶端。在這篇文章中,他們以重寫 Emoji 選擇器為例,展示了 React 在性能和代碼可維護性上給他們帶來的巨大好處,以及給用戶帶來的體驗升級。查看英文原文: Rebuilding Slack’s Emoji Picker in React

Slack 正在將 Web 客戶端遷移到 React。在最開始,我們的前端使用了 jQuery 和 Handlebars。后來,社區開發出更好的方案用于創建可伸縮的、基于數據驅動的用戶界面。jQuery 的“渲染后修改”模式直截了當,但無法與底層的模型保持同步。不同的是,React 的“渲染后再渲染”模式可以保證渲染和模型的一致性。Slack 也緊跟業界的步伐,不斷改進前端的性能和可靠性。

我們認為,要引入 React,最好的辦法就是先用 React 重寫現有產品里的某個特性——這樣我們就可以比較出新的開發流程和結果與原先的有什么不同。我們需要重寫一個組件,這個組件必須具備交互性,能夠自包含,并能夠體現 React 在性能方面的優勢。我們很快就找到了一個絕佳的組件——被重度使用且極度復雜的 Emoji 選擇器。

Slack使用React重寫Web客戶端

Virtual DOM 的優勢

閱讀這篇文章要求對 React 有一定的了解,如果你不熟悉 React,建議先閱讀一下 React 的官方文檔。簡單地說,React 是一個 JavaScript 代碼庫,可以用它方便地開發聲明式的、基于數據驅動的用戶界面。它的 API 很簡單,主要由一個組件類組成,這個類包含了一些生命周期方法。組件本身不會生成 HTML,相反,它們會生成類似 DOM 的樹,叫作 Virtual DOM。React 會比較兩個 Virtual DOM,并使用最少的操作將其中的一棵樹轉換成另一棵樹。例如,你可以告訴 React 基于新的模型數據重新渲染整個視圖,它就會以最快的速度幫你更新文本節點,就好像它有一個精靈軍團在幫你完成 DOM 的更新操作一樣。

React 擅長于將組件在單個模板上的各種行為合并在一起。舉個例子,假設一個 Slack 頻道變為未讀狀態時,你通過 JavaScript 來更新頻道的邊欄:

  • 找出這個頻道的 ID
  • 在 DOM 里查找這個頻道對應的節點
  • 將節點狀態切換成未讀(應用 CSS 類)

這個過程很簡單,不過你還得為其他事件編寫不同的處理邏輯,比如“create”、“join”、“leave”和“rename”。相反,React 把這 5 中情況合并在一起統一處理:

  • 使用新的模型數據重新渲染頻道邊欄。

我們不需要為每一種 DOM 操作編寫代碼,而是重新渲染整個組件,React 會為我們完成這個過程。React 通過讓代碼變得更通用(一刀切的模板)來簡化開發。

Emoji 選擇器

Emoji 是 Slack UI 的一個組成部分,是最理想的 React 組件。它動態、離散,只需要少量的輸入——一組 emoji、默認皮膚和用戶的 emoji 使用歷史。剛好現有的 Emoji 選擇器需要進行性能調優,因為現在不管 emoji 會不會出現在視圖里都需要進行渲染。在查找 emoji 時需要切換每個 emoji 的可見性,在重度使用時性能很成問題。新的 Slack 團隊準備了 1374 個默認 emoji,這還不包括自定義 emoji(在寫這篇文章的時候,Slack 團隊總共有 3126 個 emoji,有些團隊甚至更多)。重寫 Emoji 選擇器將會對 Slack 的日常使用產生重大影響。

Slack使用React重寫Web客戶端

我們選擇在 Storybook 里開發新的組件,Storybook 自稱是一個“會讓你喜歡上它的 UI 開發環境”。它不要求你改變開發方式,但會讓開發、測試和代碼審查變得更有趣。你可以在 Storybook 里通過指定不同的屬性來定義不同版本的組件。我們為 Emoji 選擇器增加了一個新皮膚和幾種 emoji 查找方式。

組件布局

React Emoji 選擇器的根組件是有狀態的,而子組件則是無狀態的。我們按照慣例把每個組件導出到單獨的文件里。結構如下所示:

Header

  • 分類選項卡:列出了 emoji 的類別,每個類別都有一個“jump to”鏈接。
  • 搜索框:通過 emoji 的名稱或別名過濾 emoji。

Body

  • 固定的頭部:顯示當前類別選項卡的名稱。
  • emoji 列表:所有類別的 emoji 虛擬列表。

Footer

  • emoji 預覽:當前選擇的 emoji 大圖預覽。
  • 皮膚選擇器:顯示當前的皮膚,并可以切換到其他皮膚。
  • 快捷動作(可選的):emoji 的子集,用于快速回復消息。

React 為編寫無狀態組件提供了兩種方式:PureComponent 類和 function。function 更為簡單一些,不過它們在每次合并時都會進行渲染,會影響性能。React 團隊計劃對 function 進行優化,不過目前最好還是避免使用它們。于是我們選擇了 PureComponent,它預定以了 shouldComponentUpdate 方法,這個方法可以防止在遇到相同屬性時進行更新操作。

React 是一個視圖層,把它與自己開發的應用集成要比把它與標準的框架集成直截了當得多。我們不應該破壞 Emoji 選擇器的封裝性,這樣才能很好地與 Slack 現有的模式集成在一起——我們希望這個組件就像是從一個端到端的 React 應用里拿出來的一樣。為了保持選擇器的純凈,我們在現有的模塊系統里創建了一個輕量級的適配器。適配器掛載選擇器組件,抽取模型數據,并監聽來自外部的信號。采用這種模式,我們可以在開發新功能的同時逐步地遷移代碼庫。

新的開發流程

雖然使用 React 進行開發是一件很愉悅的事情,但將它集成到我們已有的開發流程里卻不是那么一回事——至少在一開始不是那么令人愉快。在那個時候,Slack 使用的是自己開發的前端構建管道,沒有所謂的導入、依賴或者復雜的轉換(比如 transpilation)。我們決定采用 JSX 語法和 ES2015+,并使用 Babel 和 webpack 在本地構建 Emoji 選擇器的資源。

我們預期簽入本地編譯的代碼會很痛苦,但我們低估了接連發生的合并沖突和依賴管理問題是多么令人抓狂。最后,我們嘗試將 webpack 集成到我們的開發和 staging 環境里,目標是無縫地替代已有的工作流。為此,我們做了如下的工作。

  • 基于 webpack-dev-server 開發了一個服務,當相關資源和依賴發生變更時,自動編譯本地開發服務器上的資源。
  • 支持將 webpack 資源加載到單元測試里(這樣就有可能為 React 組件編寫測試用例)。
  • 重構生產環境的構建流程,將 webpack 資源推送到我們的 CDN。

通過重寫 Emoji 選擇器,迫使我們反思我們的構建管道如何能夠以一種更健壯、更具伸縮性的方式打包資源。

性能

Slack使用React重寫Web客戶端

我們在少量的團隊里部署了新的組件,并觀察結果。我們觀察了 Emoji 選擇器在用戶使用不同的 5 種交互方式下的渲染速度,對于大部分的操作,React 表現出了顯著的速度提升。以下列出了選擇器在正常規模團隊里的不同渲染時間。

  • 第一次掛載:-270 毫秒(減少了 85%)
  • 第二次掛載:-158 毫秒(減少了 91.3%)
  • 搜索(多個結果):+27 毫秒(增加了 259%)
  • 搜索(一個結果):-25 毫秒(減少了 53.2%)
  • 重置搜索:-68 毫秒(減少了 70.1%)

最大的改進來自“第一次掛載”,從 318 毫秒到 48 毫秒,減少了 270 毫秒,也就是 85%。這要極力歸功于 react-virtualized——一個虛擬列表代碼庫——減少重新渲染 emoji 的數量。在默認視圖上,React Emoji 選擇器比 DOM 少渲染了 85%。

或許最讓人感到吃驚的變化來自“搜索(多個結果)”,時間從 17 毫秒增加到了 44 毫秒,增加了 27 毫秒。舊選擇器只是把不匹配的 emoji 隱藏起來,也就是說,當匹配到大部分 emoji 時會相對較快。但它的缺點也是顯而易見的,“搜索(一個結果)”和“重置搜索”就讓它的缺點原形畢露,因為此時它需要隱藏更多的 emoji。

未來

使用 React 重寫 Emoji 選擇器加快了渲染速度,同時簡化了代碼,讓代碼更容易維護。我們正在使用 React 重寫剩余的代碼。我們還有很多工作要做,這次重寫將為用戶的日常體驗帶來積極的影響,為此我們感到非常興奮。與此同時,我們積累了 React 的實踐經驗,可以幫助平臺更進一步。

來自: InfoQ

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!