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

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

前言

移動應用每每受限於屏幕大小,而數據內容長度的不肯定性,在不少地方都須要用列表組件來做爲數據展現的容器。對應於原生應用組件,它多是iOS的TableView,也多是Android的ListView,RecycleView。這些組件都有一些共同的特色:前端

視圖可滾動。react

可複用視圖模板。git

視圖高度隨着數據內容長度的變化而彈性變化。github

自帶性能優化。react-native

當你在糾結要不要用列表組件的時候,能夠考慮一下,你使用的場景是否須要具有以上的屬性,尤爲是性能優化。性能優化

在每一列視圖的高度都較低的狀況下,在手機上一屏的顯示內容通常不過7列左右,且不說大部分時候咱們的UI不會出這種讓人心累的設計,而是咱們不能預測出API到底會返回多少組數據回來。因此,掌握列表組件的使用,是做爲移動開發必須掌握的一項基本技能。架構

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

認識列表組件

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列表組件實戰解析》


這裏寫圖片描述

相關文章
相關標籤/搜索