Rax 系列教程(長列表)

Rax 系列教程(長列表)

引子

Rax 提供的長列表標籤有很多,在什麼場景下使用什麼列表組件,怎樣選擇列表組件性能會更好,這些問題可能會給剛接觸 Rax 的同學帶來困擾。本文結合 Rax 0.5 發佈版本對列表能力進行一次詳細的梳理。

如何讓頁面滾動

在開始正題之前先說說爲什麼要有長列表的概念,以及如何讓頁面可以滾動。

傳統的 Web 頁面天生在瀏覽器裏就是可以滾動的,我們額外引入一個滾動容器的概念好像比較多餘。但當我們做跨容器開發時,這一層概念就變的有意義。native 的頁面天生不可滾動,需要藉助滾動容器的滾動能力,比如 iOS 中的 UITableView、Android 中的 RecyclerView,通過組件的方式讓頁面的部分內容可以滾動。

寫好了一個頁面發現在 Weex 上是白屏,很可能就是滾動容器沒有撐開。真實需求中我們往往想要整個頁面滾動,首先要解決的就是屏幕高度問題。下面這段是比較常用的頁面佔滿全屏的手段。

<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}>
<RecyclerView />
</View>

對於動態設置高度的場景,我們可以通過 dom.getComponentRect 方法得到頁面可是區域的高度。

let dom = require('@weex-module/dom');
dom.getComponentRect('viewport', (e) => {
console.log(e.result, e.size);
});

如此以來我們的頁面就可以自由滾動,通過下拉刷新、加載更多能力的組合讓我們的滾動容器更貼近 Web 體驗。

現有列表與能力範圍

Rax 目前提供了很多列表組件,相關基礎組建以及主要特點如下:

  • rax-scrollview (水平滾動推薦方案)
    • Weex 上實現是 slider,支持垂直和水平的滾動
    • 無法做 cell 回收,內容過多時會有性能問題
  • rax-recyclerview (最常用高性能推薦方案)
    • Weex 實現是 list,可回收的長列表,不可水平滾動
    • 性能上有很大優化,滾動體驗流暢
  • rax-listview (RN 習慣)
    • RecyclerView 的上層包裝,對標 RN 的能力
    • 對性能和列表多樣化展示有更高要求的推薦使用 RecyclerView
  • rax-waterfall (瀑布圖場景推薦)
    • 底層實現上也是 list 的一個擴展,在 API 能力上向 ListView 靠攏

長列表基礎能力

作爲最基礎的推薦實現方案,以 rax-recyclerview 爲例,介紹幾個列表的重要功能

onEndReached

當頁面滾動到底部時,往往我們會有繼續加載的操作,Weex 上 loadmore 事件。對應到 rax-recyclerview 就是 onEndReached 屬性。

在 Weex 中 onEndReached 出發後如果 cell 個數沒有發生變化,文檔的高度沒有繼續撐開,則不會重複加載 onEndReached,這種保護措施讓我們避免了重複加載,但同時也引入了另外一個問題。

上面這個例子展示的邏輯是切換 tab 改變同一 list 的功能,當我切換 tab 後更新列表的數據條數與上一個 tab 觸發 onEndReached 的位置相同時,會發現 onEndReached 失效了。原因就是不會重複觸發導致的,解決方案就是使用 列表的 resetScroll 方法重置列表的滾動情況。下面是示例代碼:

this.refs.list.resetScroll();

refresh

下拉刷新是 web 瀏覽器的原生體驗,Weex 上的模擬是通過列表標籤內的 RefreshControl 組件實現,注意的是 RefreshControl 需要放在列表的第一個元素,如果有標籤在 refresh 之前會導致 RefreshControl 無法正常展示。

appear

在上手教程中介紹過這個事件,onAppear 事件可以讓我們在元素出現的時候做一些事情,在 Web 上 Rax 的 framework 同樣提供了 Appear 事件用來抹平與 Weex 的差異。appear 的一些注意點如下

  • appear 需要綁定在 滾動容器內不,不然 Weex 上無法生效
  • appear 的能力實際上是基於 onScroll,過多的 appear 對於滾動性能會稍有影響
  • appear 是一個滑動過程中可能頻繁觸發的事件,在這裏的 setState 邏輯需要自己把控好

onScroll

滾動過程中我們需要實時的做一些操作時會用到 onScroll,onScroll 時計 setState 更新內容是一個成本很大的事情,需要注意是否過頻繁的操作會引起頁面的卡頓,另外在滾動過程中的動畫操作我們推薦使用 BindingX ,這個實現方案可以減小通信成本達到性能的提升,如下示例:

完整 demo 在這裏,下面代碼展示滾動過程中一個元素的動畫

binding.bind({
eventType: 'scroll',
anchor: list,
props: [
{
element: image,
property: 'transform.translateY',
expression: image_origin
},
]
}, function(e) {
});

頁面的組織

簡單可滾動頁面

撐滿設備屏幕的 View 內部的滾動容器默認就是高度撐開的,此種場景是我們業務中用到最多也是最基礎的滾動場景。

<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}>
<RecyclerView />
</View>

頁面部分固定

如果頁面中有部分是固定的其餘部分可以滾動我們可以採用如下方式,這種場景通常用來作爲頂部導航或者底部 bar。

<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}>
<View style={{ height: 80 }} />
<RecyclerView />
</View>

模塊吸頂

樓層吸頂是一個較爲常見的會場類頁面需求,通常的實現方案是 RecyclerView.Header 標籤,需要注意的是 Rax 0.5 版本中還未對 RecyclerView.Header 做 web 上的實現,需要業務上處理,可以將樣式設置爲 fixed,或者將要吸頂元素拷貝到列表外部。上面的演示圖效果更爲複雜,使用到了 binding。

<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}>
<RecyclerView>
<RecyclerView.Header />
</RecyclerView>
</View>

橫滑切換多頁面

性能的優化帶來的是體驗的提升,我們可以不再拘泥於刷新頁面來切換頁面。這就有了橫滑翻頁的嘗試。其主要思路就是通過手勢來進行橫滑拖拽。

<View style={{width: 750, position: 'absolute', top: 0, bottom: 0}}>
<Tab ... />
<TabController ... >
<TabPanel><SamplePage index="0" /></TabPanel>
<TabPanel><SamplePage index="1" /></TabPanel>
<TabPanel><SamplePage index="2" /></TabPanel>
</TabController>
</View>

此處我們還將引申出另外一個文章,《Rax 系列教程(單頁)》敬請期待

模擬滾動嵌套

隨着頁面交互形式的越來越複雜,更豐富的體驗效果不斷的出現。如上圖橫滑頁面的部分上方出現一個公共區域。目前業務中較的實現方案是滾動下方的容器過程中去動態改變一個靜態的 header,頁面組織形式如下:

<View style={{width: 750, position: 'absolute', top: 0, bottom: 0}}>
<Parallax> header 部分 </Parallax>
<Tab ... />
<TabController ... />
</View>

其中 Parallax 的部分也可以用 View 加動畫的方式實現,不過這種效果畢竟是模擬一個滾動嵌套,還不完美。

長列表使用技巧

水平與垂直滾動嵌套

垂直滾動容器中往往會有水平橫滑的場景,實現的方案有很多種,比如 Slider 組件可以完成水平的滾動輪播,Tabheader 可以作爲可橫滑的 tab,如果想要更佳令我我們還可以用 ScrollView 自己實現一個水平的滾動。

在垂直與水平嵌套的場景中需要注意一點,就是水平滾動容器並不能盡興節點的回收,所以橫滑內容過長可能會引發性能問題,需要合理規劃橫滑內容。

手勢衝突

手勢動畫我們可以用與 RN 能力對其的 PanResponder,當然我們更推薦性能更加優秀的 BindingX 手勢,在 Rax 的標籤元素上綁定 onTouchStart 這樣的事件也是支持的。在這些能力支持的基礎上有一些坑也是需要我們注意的,比如手勢與滾動行爲相互吃掉。垂直長列表默認就有上下滾動的行爲,此時我們想要做一些手勢處理的需求時可能要先考慮一下手勢的方向會不會被滾動容器的滾動所影響。

一種情況是垂直滑動手勢,頁面垂直滾動時儘量避免垂直的手勢行爲。雖然我們可以通過禁用滾動等方式模擬手勢滑動,但目前 iOS 和安卓仍然有支持程度不同的兼容問題。所以如果垂直滑動時想要做一些事情,推薦使用 onScroll 事件 或者 BindingX 的 scroll 方法來解決。

另一種情況是水平橫畫手勢,如上圖。頁面由 4 個 tab 組成,橫畫頁面可以切換 tab,此事如果我們對容器再綁定水平的華東手勢,就會對橫畫切 tab 的行爲造成影響。爲了避免這種衝突,我們推薦使用原生 slider 進行頁面內的水平滾動操作,省區我們自己處理這一層衝突。

電梯跳轉

每年大促的頁面中我們幾乎都能看到電梯的身影,實現的基本思路是 Weex 下利用 Weex dom 模塊的 scrollToElement 方法跳轉到頁面的制定元素,h5 下用錨點進行條轉。此時需要頁面樓層之間斤兩撐開,避免頁面抖動的情況。

還有一種方式是利用滾動容器的 scrollTo 方法跳轉指定舉例,此方式需要在跳轉前嚴格計算每個樓層的高度。

模塊的順序保證

在長列表數據更新或者模塊更新的過程中,如果沒有指定每個 cell 之間的順序就可能出現樓層錯位問題,指定的方式就是每個 cell 指定唯一的 key。如果摸個模塊返回的是多個 cell 的暑促,那除了每個 cell 指定 key 這個模塊也需要指定一個唯一的 key。

視差滾動

視差滾動需求目前提供了兩種解決方案,一種是 Weex 的 parallax 標籤,另一種是使用 BindingX 進行視差滾動的模擬。

Web 與 Weex 列表上的不同

此處說明配合 Rax 0.5 版本。

  • 下拉刷新: web 有原生的下拉刷新體驗,RefreshControl 僅有 weex 的實現
  • 吸頂:web 上沒有實現 RecyclerView.Header 的吸頂效果
  • 電梯:樓層跳轉方式不同,web 上採用錨點或距離的方式,Weex 採用 scrollToElement 方法
  • 回收機制:web 上沒有節點的回收
  • appear:weex 原生支持 appear,並且只能在滾動容器內部才能生效,web 是根據元素是否在可視區域進行模擬的

長列表的性能注意點

  • 當列表數據過長時,不推薦用 ScrollView 作爲頁面級別的滾動容器,RecyclerView 有更好的滾動性能(非可視區域 cell 的回收機制)
  • RecyclerView 的 cell 拆分粒度越細越好
  • 同一 cell 內部不要放置太多圖片,保持儘量簡潔一致的 cell 結構利於原生 tableview cell 視圖複用
  • cell recycle = false 屬性會破壞 cell 的內存回收機制
  • WaterFall 的 header 沒有回收機制,不建議瀑布圖頭部 header 過長
  • 嵌套太深不利於回收,建議最大深度不超過 15
  • 列表內如果有大量視頻需要控制視頻標籤數量,建議非可視區域的視頻區塊用圖片代替
  • 更新列表數據時,如果 cell 內部有類似事件綁定 onClick={()=>{}} 每次渲染會實例化新的 function 導致列表內容 diff 前後對比不一致,會觸發 cell 的重新 render
  • 爲避免長列表內元素的重複渲染,可在組件實現上 shouldComponentUpdate 時機可以將其 return 掉
  • 列表內存暴漲、滑動卡頓 優先排查頁面是否有頻繁的 setState

題圖: https://unsplash.com/photos/V7gVxlUE5aY By @Toa Heftiba

Rax 系列教程(長列表)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章