GitChat · 移動開發 | 征服React Native—列表元件

GitChat · 移動開發 | 征服React Native—列表元件

GitChat 作者:Li Luo
原文:征服React Native—列表元件
關注公眾號:GitChat 技術雜談,一本正經的講技術

前言

移動應用往往受限於螢幕大小,而資料內容長度的不確定性,在很多地方都需要用列表元件來作為資料展示的容器。對應於原生應用元件,它可能是iOS的TableView,也可能是Android的ListView,RecycleView。這些元件都有一些共同的特點:

檢視可滾動。

可複用檢視模板。

檢視高度隨著資料內容長度的變化而彈性變化。

自帶效能優化。

當你在糾結要不要用列表元件的時候,可以考慮一下,你使用的場景是否需要具備以上的屬性,尤其是效能優化。

在每一列檢視的高度都較低的情況下,在手機上一屏的顯示內容一般不過7列左右,且不說大部分時候我們的UI不會出這種讓人心累的設計,而是我們不能預測出API到底會返回多少組資料回來。所以,掌握列表元件的使用,是作為移動開發必須掌握的一項基本技能。

下面,就讓我們來看看React Native的框架之下,又有哪些列表元件可供使用呢。

認識列表元件

ListView

是React Native最早誕生的列表元件,可以方便的用來顯示具有縱向滾動屬性的資料,實現最基本的兩個屬性 dataSource 和 renderRow就能讓它工作起來。它也支援更多高階的屬性,如section和sticky section headers, header,footer,onEndReached等,以及一定的效能優化。為了使ListView滾動更加平滑,在動態的載入一個大的資料(無盡列表)時,可以這樣一些優化:

  • 只優化發生變化的列:rowHasChanged就是通過比較資料是否發生變化,來判斷ListView的row是否需要重繪。

  • 限定行渲染的速度:預設每次只渲染一行(可以由pageSize屬性來控制),把工作分解為較小的塊,以減少渲染時丟幀的機率。

基本用法:

<ListView
dataSource={this.state.dataSource}
renderRow={(rowData, sectionID, rowID) => this.cell(rowData, rowID)}
/>

但是,ListView在處理無盡列表時,表現卻不盡人意,它並不會把檢視以外的元素從VirtualDom上面移除,在列表長度長度較大時,滾動時往往出現掉幀情況,記憶體也佔用隨著列表的滾動,消耗急劇增加。如上圖中所示。

enter image description here

NOTE 從使用者的角度來看,FlatList和SectionList是ListView的一次裂變。不過它們並不是ListView所派生出來,而是同屬於VirtualizedList的具體實現,比起ListView,它們在效能上做了極大的改進,最早出現在0.43版本,但在該版本中的bug較多,如果想要使用建議升級RN到0.44以上。

FlatList

顧名思義,它是一個扁平化的列表,砍掉了section的支援,同時,增加了很多移動端常用的玩法:支援橫向滑動,下拉重新整理,separator,ScrollToIndex等。相比ListView,效能上也得到了巨大的提升,一般情況下,推薦使用FlatList。基本用法:

<FlatList
data={[{key: 'a'}, {key: 'b'}]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>

SectionList

如果需要把列表進行分類展示,同時給每個分類設定頭部,比如像地址,分類的產品,分類的相簿等,SectionList就是最好的選擇。

基本用法:

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // homogenous rendering between sections
​    {data: [...], key: ...},
​    {data: [...], key: ...}
]}
/>

VirtualizedList

如果你需要更強的定製化的列表,RN的FlatList和SectionList已經不能滿足你要的效果,可以在VirtualizedList上增加Wrapper來實現你的定製化。

虛擬化通過維護有限寬度的渲染視窗,並把渲染視窗之外的所有item替換為空,這樣大大提高了大型列表的記憶體消耗和效能,滾動起來也更加的流暢。

常用屬性

data:源資料,預設為Array<{key: string}>型別。

renderItem:對應一個資料Item的顯示。

ListHeaderComponent:列表頭部。

ListFooterComponent:列表尾部。

ListEmptyComponent:當列表為空的時候,顯示的component.

horizontal:是否水平方向展示。

onEndReached:已經滾動到底部的callback.

onRefresh:下拉重新整理的callback.

refreshing:標記是否正在重新整理。

initialNumToRender:如果有“回到頂部”的需求,建議設定該屬性,建議為剛好滿屏時候的列數。

Multi-column

如果要實現GridView的效果,你可以FlatList自帶的屬性numColumns和columnWrapperStyle配合使用,實現多列:

  <FlatList
horizontal={this.state.horizontal}
data={this.props.data}
numColumns={2}
columnWrapperStyle={styles.multiColumns}
renderItem={({ item, index }) => this.props.renderRow(item, index)}
/>

下拉重新整理

ReactNative在新的列表元件當中,已經提供了下拉重新整理的功能,可以通過快速的設定refreshing和onRefresh方法來實現:

  <FlatList
data={this.props.data}
renderItem={({ item, index }) => this.props.renderRow(item, index)}
refreshing={this.state.refreshing}
onRefresh={() => {
this.setState({refreshing: true})
this.props.getProducts(this.state.pageIndex)
.then((items) => {
this.setState({refreshing: false})
})
.catch((error)=> Alert.alert(error.message))
}
}
/>

超長列表的優化

對於列表的優化,主要集中在兩個方面,一個是記憶體消耗,一個使用者響應,使用者響應又可以分為:滾動是否流暢,對點選等操作響應速度是否迅速。我們先來看看新的列表元件VirtualizedList都給我們帶了哪些改進:

PureComponent: 減少不必要的渲染,如果props屬性不變,它就不會重繪。 這裡需要我們確保在更新props後不是===,否則UI可能無法更新更新。

限定渲染視窗: 通過維護有效專案的有限渲染視窗並把渲染視窗之外的所有元素替換為空(Blank),大大提高了大型列表的記憶體消耗和效能。

低優先順序渲染視窗以外的區域:視窗適應滾動行為,如果專案遠離可見區域,則專案將以低優先順序(在任何執行的互動之後)逐漸呈現,否則為了最小化檢視空格的可能性。。

非同步渲染:內容將非同步地渲染在螢幕外。 這意味著可能滾動會比填充率更快,看到空白的內容。

可以看到,新的列表元件在記憶體消耗上做出了改進,滾動的流暢度得到了較大的提升,但是,對於使用者點選等操作的響應的速度應該算是沒有帶來利好,反而在滾動中會出現白屏。

FlatList滾動時閃現白屏

從截圖中可以看到,FlatList在流暢度的提升還是很明顯的,不過,在急速滾動的情況下,中間會出現白屏,這對於使用者體驗上來說很不友好。這裡,我們可以參考VirtualizedList提供的屬性來做優化。

windowSize: 限定繪製的最大數目,預設為21。

maxToRenderPerBatch:一次繪製的最大數目。

updateCellsBatchingPeriod:更新繪製的間隔時間。

removeClippedSubviews:移除看不見的subview,目前還有bug,可酌情使用。

initialNumToRender:首次繪製的數目。

getItemLayout:可以用來幫助我們跳過高度和位置的重新運算,當我們的每一個Item高度一致時,設定這個屬性可以極大的提高渲染效率。

getItemLayout={(data, index) => (
{length: ITEM_HEIGHT, offset: (ITEM_HEIGHT  SEPARATOR_HEIGHT) * index, index}
)}

不過嘗試了幾種以上屬性的組合,感覺並不能解決很好的解決白屏問題,這個問題的修復只能期待更新的版本,大家也可以嘗試主動提交PR。

以上的操作都是VirtualizedList提供的方法,那對於ListView的卡頓問題,我們也可以模仿FlatList的做法去改進:

置空非顯示區域的元素:把已經移出螢幕的Item給置空。

一次載入多個元素:增加一次繪製的元素個數。

重用列表項:限定只渲染指定的N個item,從N 1之後就重用之前建立的Item。

RN列表元件特別愚蠢的一點是,對於移出介面的節點它並不會去銷燬,而是依然保留在Dom結構上,這裡,新鮮出爐VirtualizedList也並沒有解決問題。

可展開的多級列表

如果只是為了實現類似於Android的ExpandableList的展開收攏效果,而且只有一級子目錄的情況下,SectionList就已經可以滿足我們的需求,只需要為SectionListHeader繫結onPress事件,在事件中修改展開收攏的狀態即可。

如果是多級的列表,比如像這樣的地址: 北京.朝陽區.望京街道.XX大廈C棟… 像這種層級比較深的情況下,我們可以用什麼方法快速實現呢?這種情況下,建議大家採用FlatList。有興趣一起探討的同學,歡迎在6月5日加入我們的線上討論。

你還需要知道的事

目前的列表元件,不管是Android還是iOS平臺,都還是比較順暢而且使用方便,如果不是對使用者體驗有著特殊要求,以上的內容應該已經可以滿足大部分的使用情況。不過,它們也都有各自的缺陷。在處理超長列表的時候,ListView的短板在於滑動卡頓和記憶體消耗大,FlatList的問題就在於快速滑動時,可能會看到空白的區域,始終都不盡人意。

除了在RN的元件上做優化,我們還能為我們的列表體驗優化做點什麼呢?

  1. 分頁展示:儘量不要一次性載入過多的資料,可以的話,給你的資料加上分頁載入,比如一次10條資料,讓使用者主動去下拉重新整理,這個用FlatList來實現還是非常簡便的。

  2. 使用Native原生元件:需要注意的是,React Native為我們提供了可以跨平臺使用的列表元件,它們並不是對應的原生元件,所以它們並不是直接使用到了原生元件的特性,從原始碼來看,FlatList只是用到原生的ScrollView及其事件,但並沒有使用到原生RecycleView帶來的好處。所以,如果你的專案對支援超長列表以及使用者體驗要求高的雙重要求,你完全可以選擇原生元件,這樣,才能更好的利用平臺特性。

參考閱讀

作者簡介:羅麗,ThoughtWorks高階軟體工程師,高階軟體工程師,移動技術開發顧問,能熟練在專案中運用TDD及Refactor等技術,擁有豐富的軟體開發經驗, 多年海內外專案交付的經歷,擅長Android,iOS等多平臺開發技術,目前任職於ThoughtWorks海外事業部,曾在多個大型移動應用架構中擔任技術顧問。


實錄:《羅麗:React Native列表元件實戰解析》


彩蛋

重磅 Chat 分享:《一場 Chat 讓你搞清 BAT 程式設計師的技術職級》

分享人:
勝洪宇,一線網際網路公司前端技術組長,掘金簽約作者,前端部落格博主,所講課程幫助超過20萬前端小夥伴學習。
Chat簡介:
很多程式設計師嚮往進入 BAT 這樣的大型網際網路公司,但是又不知道他們如何評定技術職級。
– 阿里集團薪資職級如何劃分?讓你快速得到馬雲的青睞。
– 在百度明白這些,你將快速晉升。
– 騰訊職級裡的小祕密,這樣工作你會更強。
一場 Chat 讓你搞清 BAT 的技術評價體系,為您進入超級網際網路公司指明技術方向,時刻做好準備!如果您希望您的技術團隊也像這些網際網路巨頭一樣強大,本場 Chat 我將幫您馬上模仿建立有效的技術職級體系。

想要免費參與本場 Chat ?很簡單,「GitChat技術雜談」公眾號後臺回覆「BAT」

這裡寫圖片描述