行動裝置 App往往受限於螢幕大小,而資料內容長度的不確定性,在很多地方都需要用列表組件來作為資料展示的容器。
對應於原生應用組件,它可能是iOS的TableView,也可能是Android的ListView,RecycleView。
這些組件都有一些共同的特點:
1、視圖可滾動。
2、可複用視圖模板。
3、視圖高度隨著資料內容長度的變化而彈性變化。
4、內建效能最佳化。
當你在糾結要不要用列表組件的時候,可以考慮一下,你使用的情境是否需要具備以上的屬性,尤其是效能最佳化。
在每一列視圖的高度都較低的情況下,在手機上一屏的顯示內容一般不過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上面移除,在列表長度長度較大時,滾動時往往出現掉幀情況,記憶體也佔用隨著列表的滾動,消耗急劇增加。如上圖中所示。
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在流暢度的提升還是很明顯的,不過,在急速滾動的情況下,中間會出現白屏,這對於使用者體驗上來說很不友好。這裡,我們可以參考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的做法去改進: