Slack使用React重寫Web客戶端

知與誰同發表於2017-07-04

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

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

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

20170614113616885.png

  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的日常使用產生重大影響。

20170614113616917.png

我們選擇在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選擇器,迫使我們反思我們的構建管道如何能夠以一種更健壯、更具伸縮性的方式打包資源。

效能

20170614113616949.png

我們在少量的團隊裡部署了新的元件,並觀察結果。我們觀察了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的實踐經驗,可以幫助平臺更進一步。



本文轉自d1net(轉載)


相關文章