作者:閑魚技術——龐止
閑魚會玩社群的重要陣地:會玩圈子今年年初已經上線啦~
作為一款承載着「基于興趣聚集同好人群」的社群型産品,相較于正常導購型産品來說,在業務複雜度、互動複雜度、性能體驗穩定性要求上都要高出許多,像多角色區分、嵌套滾動、多形态 Feeds 無限加載、顔文字等特殊字元處理、頁面直開、視訊播控等場景都是導購場景很少遇到的。
本文将圍繞着會玩圈子的前端設計、開發過程中遇到的典型問題進行介紹。
關鍵設計

如上圖為會玩圈子的設計大圖,可以看到整體的業務邏輯相對較為複雜。并且由于應用中存在多種角色狀态,不同角色的使用者展示界面和操作邏輯也有差異點存在,前端同學進行開發上手的成本較高。
為了降低前端同學在開發過程中對于全局業務了解的成本,減少溝通中資訊傳遞容易遺漏的問題。我們在設計之初首先進行了最小子產品的拆分工作。以子產品次元來配置設定相應的工作,将子產品間存在資料共享和資料通信行為進行梳理拆解,以最優方式來配置設定資料狀态維護的最小閉環,降低元件間耦合度。
1. 子產品拆分
在此處定義的子產品不是前端日常開發中所講的子產品元件拆分,而是能夠獨立閉環自成體系的一個業務單元。這樣在項目過程中除了部分元件間資料傳遞需要做預先約定外,前端同學僅僅隻需要專注于自己所負責的互動場景即可。


以圈子子產品首頁為例,根據設計稿我們将它拆分為了三個獨立的業務子產品:圈子資訊子產品、資訊流子產品和浮層元件子產品,他們無論從功能上還是展示上都完全不同。
- 圈子資訊子產品: 偏展示型子產品。互動較少,根據業務資料展示相應的資訊子產品内容,需要根據使用者目前的身份展示不同類型的子產品元件,并且可以根據目前使用者的身份來進行權限校驗,在未符合時進行友好Tip提示的能力。
- 資訊流子產品: 偏互動型子產品。需要支援多個清單在Tab下嵌套滾動的能力,子產品本身需要維護使用者關注狀态表、黑名單篩選表和視訊播放清單,友善子產品中的子元件進行資料共享。并且清單具備單排流和瀑布流兩種布局模式,清單内元素存在商品、文章内容兩種類型卡片。支援評論、點贊檢視原圖、視訊播放、觸發浮層等多種互動能力。
- 浮層子產品:通用型子產品。允許開發者根據業務需求注冊相應的子產品元件,并且允許配置相應的展示位置、動畫效果和圖層index
2. 狀态值拆分
由于在圈子中有非常多需要共享資訊的場景存在,例如使用者相關資料、圈子基礎資訊等,僅僅隻是基于業務子產品閉環來拆分狀态在此處就不适合了。
是以在完成子產品拆分之後我們對圈子首頁的狀态資料進行了梳理,根據元件最小渲染原則來對狀态進行相應的拆分,如下圖所示:
全局狀态
如上圖,對于有共享訴求的狀态變量,我們優先将這些狀态值彙總到一起以友善統一處理。
但由于是C端場景,互動複雜度不高并且考慮到資源包的大小會對使用者體驗有一定影響。此處的全局狀态的管理方案我們選擇了直接使用Rax原生的
useReducer + useContext
來進行處理,并将擷取對應執行個體
context
的方法Hooks化以友善開發同學使用,簡單demo代碼如下所示:

業務元件狀态
對于非共享型的資料,則要求放到業務子產品中元件渲染影響最小的容器層來進行維護,以單排流的文章清單為例:
- 卡片的初始資料通過props形式傳入,單一文章的互動性資料都保留在文章元素元件一層來維護。
- 清單容器中除了基礎狀态資訊外,僅僅隻做視訊播放的控制,不額外觸發容器級的重渲染。
實施調優
在多個業務子產品進行組合調試的過程中,我們發現互動體驗依然有許多不盡人意的問題點:
- 展示子產品過多的情況下,如果在多個Tab下進行資料加載切換過後整個頁面的互動會出現明顯的卡頓感:比如點選彈出浮層會有明顯的等待時延,翻頁切換Tab時對應的下标移動會不同步。
- 浮層容器中注冊的部分元件由于依賴共享變量,在共享變量變化時也會觸發不必要的重渲染:效果為會跟着閃動一下。
- 網絡狀況不穩定的情況,頁面展示不夠友好的;從使用者點選路由跳轉到首屏頁面展示的等待時間過于明顯,與我們要求的頁面直開效果相差甚遠。
1. 減少Context.Provider重渲染
使用0到1:閑魚高複雜度高性能社群圈子開發實錄
盡管可以提升狀态值傳遞的便捷性,但是伴随的問題也相當明顯:每一次狀态值更新變化都會觸發整個Context
和下面的子元件重新渲染。Context.Provider
這與我們預期的渲染流程不一緻,畢竟我可能隻是調整了一個
CircleHeader
元件所依賴的值,沒必要底下
CircleSlider
元件及其中的清單元件都需要跟着做調整渲染,這個代價是我們無法接受的。
通過在社群中尋找相應的解決方案,我們發現還是有一定技巧來解決這個問題的:
Context.Provider
其實元件也保持着
Rax
元件的一緻規則:
props.children
作為傳入屬性,它如果保持不變就不會觸發值diff,進而也就不會出現重渲染的問題了。
那如何才能做到讓
Provider
不會由于
props.children
的變化産生重渲染呢?通過社群提供的資料,我們發現每次執行的都是JSX轉義後的
createElement(xxx)
。由于每次執行産生的子元件都不一緻,是以會導緻不必要的重渲染。
為此我們将
Context.Provider
單獨拆分成為一個專門用于傳狀态值的高階元件,将子元件以
props.children
的形式傳入:
通過這種方式,我們将
CircleApp
變為了
Stateless Component
。隻有在首次初始化元件時進行渲染,之後
Provider
值變化時頁不會重新導緻
GlobalContextProvider
執行
createElement(CircleApp``)
來重新建立元件執行個體了,減少不必要的js執行。
2. 調整元件結構

如上圖可以看到在圈子中存在較多彈出浮層元件的場景,在初版設計過程中考慮到浮層元件由于也需要使用到全局共享變量。是以在設計元件結構之初,将浮層容器元件放到了全局共享變量的
GlobalContextProvider
元件之内。
但在實際體驗過後發現,盡管對于内部的浮層元件擷取共享變量較便利了,但回報出來的問題也相當明顯:在使用中低端機型時,如果頁面加載的資料過多後會出現明顯的延遲感。并且浮層元件僅僅在真實展示的時候才需要用到相應的狀态值,非展示時其實不需要關系這些資料的具體内容。

為此我們調整了浮層容器元件和首頁常駐元件的層級結構,如上圖所示:将常駐元件容器和層容器由原來的嵌套結構優化為了并行結構,兩元件之間的資料通信通過方法調用來觸發。這樣調整之後優點相對比較明顯:首先浮層元件可以更加通用、複用性更強。所有所需參數都是通過傳參的方式傳入,不需要再強依賴全局共享狀态,對于開發同學來說維護起來的成本更低。其次因為減少對共享狀态值的依賴,子元件不必要的重渲染也都得到了優化。對于中低端機型也能提供相對更好的互動體驗。
3. 首屏體驗優化 + 容災機制
在去年下半年的體驗優化更新戰役中閑魚的前端頁面體驗都有了很大的提升:頁面首屏等待時間大幅度降低、内容展示更加友好,各個頻道頁接入漸進式首屏後使用者能更快的檢視到内容資料。
但在圈子開發的過程中,我們發現對于個性化推薦的場景之前提出的漸進式首屏方案無法較好的支援。為此我們選擇了降級方案,調整了從圈子廣場頁到圈子首頁及相關子頁面的路由跳轉邏輯。

如上圖,通過制定上下遊頁面之間的資料緩存約定來達成容災和提高互動體驗是目的。在每次頁面路由跳轉時都将相應的業務資料進行緩存,在下一級頁面對消費相應的緩存資料。這樣不僅可以在網絡環境較差的情況下提升使用者的體驗。同時在接口報錯時進行可以起到最低程度的有效兜底,避免使用者體感過差。


優化前 優化後
在此基礎上為了提升頁面首屏的渲染速度,我們接入提升資料預取方案和離線包緩存方案。将首屏頁面渲染過程中最為耗時的資源包加載過程和首屏接口請求過程做了并行化處理,進而降低了首屏展示的等待時長。

優化前後效果對比:


優化前 優化後
後續展望
會玩圈子的首個版本在遭遇各種小問題後終于順利上線了。在這個過程中解決了部分在之前電商導購場景下未經曆過的問題:例如角色權限管控,多狀态值管理等問題。這些經驗的沉澱對于之後閑魚社群内複雜C端應用體系的成長可以提供一定的助力。
但目前仍有許多的問題點待我們思考優化:
- 目前圈子首頁的首屏平均可互動時長為1000ms左右,使用者從點選入口到進入首頁内浏覽基本無需等待。但我們相信通過根據裝置類型來做區分,在進入頁面之初降級部分中低端機非必須能力能夠為這一類使用者提供更快的互動體驗。
- 為了突出社群内不同圈子個性點,相信自定義的裝修能力以及定制插件能力在之後是必不可少的。要如何能夠基于現有的架構體系快速接入這些業務訴求,也是我們在現有能力上需要預先思考到的
- 根據業務訴求的變化,如何将從業務子產品中産生的元件盡量做到更通用化并且支援多種容器也是需要解決的。