GitChat 做者:Li Luo
原文:征服React Native—列表組件
關注公衆號:GitChat 技術雜談,一本正經的講技術html
移動應用每每受限於屏幕大小,而數據內容長度的不肯定性,在不少地方都須要用列表組件來做爲數據展現的容器。對應於原生應用組件,它多是iOS的TableView,也多是Android的ListView,RecycleView。這些組件都有一些共同的特色:前端
視圖可滾動。react
可複用視圖模板。git
視圖高度隨着數據內容長度的變化而彈性變化。github
自帶性能優化。react-native
當你在糾結要不要用列表組件的時候,能夠考慮一下,你使用的場景是否須要具有以上的屬性,尤爲是性能優化。性能優化
在每一列視圖的高度都較低的狀況下,在手機上一屏的顯示內容通常不過7列左右,且不說大部分時候咱們的UI不會出這種讓人心累的設計,而是咱們不能預測出API到底會返回多少組數據回來。因此,掌握列表組件的使用,是做爲移動開發必須掌握的一項基本技能。架構
下面,就讓咱們來看看React Native的框架之下,又有哪些列表組件可供使用呢。app
是React Native最先誕生的列表組件,能夠方便的用來顯示具備縱向滾動屬性的數據,實現最基本的兩個屬性 dataSource 和 renderRow就能讓它工做起來。它也支持更多高級的屬性,如section和sticky section headers, header,footer,onEndReached等,以及必定的性能優化。爲了使ListView滾動更加平滑,在動態的加載一個大的數據(無盡列表)時,能夠這樣一些優化:框架
基本用法:
<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以上。
顧名思義,它是一個扁平化的列表,砍掉了section的支持,同時,增長了不少移動端經常使用的玩法:支持橫向滑動,下拉刷新,separator,ScrollToIndex等。相比ListView,性能上也獲得了巨大的提高,通常狀況下,推薦使用FlatList。基本用法:
<FlatList data={[{key: 'a'}, {key: 'b'}]} renderItem={({item}) => <Text>{item.key}</Text>} />
若是須要把列表進行分類展現,同時給每一個分類設置頭部,好比像地址,分類的產品,分類的相冊等,SectionList就是最好的選擇。
基本用法:
<SectionList renderItem={({item}) => <ListItem title={item.title} />} renderSectionHeader={({section}) => <H1 title={section.key} />} sections={[ // homogenous rendering between sections {data: [...], key: ...}, {data: [...], key: ...} ]} />
若是你須要更強的定製化的列表,RN的FlatList和SectionList已經不能知足你要的效果,能夠在VirtualizedList上增長Wrapper來實現你的定製化。
虛擬化經過維護有限寬度的渲染窗口,並把渲染窗口以外的全部item替換爲空,這樣大大提升了大型列表的內存消耗和性能,滾動起來也更加的流暢。
data:源數據,默認爲Array<{key: string}>類型。
renderItem:對應一個數據Item的顯示。
ListHeaderComponent:列表頭部。
ListFooterComponent:列表尾部。
ListEmptyComponent:當列表爲空的時候,顯示的component.
horizontal:是否水平方向展現。
onEndReached:已經滾動到底部的callback.
onRefresh:下拉刷新的callback.
refreshing:標記是否正在刷新。
initialNumToRender:若是有「回到頂部」的需求,建議設置該屬性,建議爲恰好滿屏時候的列數。
若是要實現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的作法去改進:
置空非顯示區域的元素:把已經移出屏幕的Item給置空。
一次加載多個元素:增長一次繪製的元素個數。
重用列表項:限定只渲染指定的N個item,從N+1以後就重用以前建立的Item。
RN列表組件特別愚蠢的一點是,對於移出界面的節點它並不會去銷燬,而是依然保留在Dom結構上,這裏,新鮮出爐VirtualizedList也並無解決問題。
若是隻是爲了實現相似於Android的ExpandableList的展開收攏效果,並且只有一級子目錄的狀況下,SectionList就已經能夠知足咱們的需求,只須要爲SectionListHeader綁定onPress事件,在事件中修改展開收攏的狀態便可。
若是是多級的列表,好比像這樣的地址: 北京.朝陽區.望京街道.XX大廈C棟... 像這種層級比較深的狀況下,咱們能夠用什麼方法快速實現呢?這種狀況下,建議你們採用FlatList。有興趣一塊兒探討的同窗,歡迎在6月5日加入咱們的在線討論。
目前的列表組件,不論是Android仍是iOS平臺,都仍是比較順暢並且使用方便,若是不是對用戶體驗有着特殊要求,以上的內容應該已經能夠知足大部分的使用狀況。不過,它們也都有各自的缺陷。在處理超長列表的時候,ListView的短板在於滑動卡頓和內存消耗大,FlatList的問題就在於快速滑動時,可能會看到空白的區域,始終都不盡人意。
除了在RN的組件上作優化,咱們還能爲咱們的列表體驗優化作點什麼呢?
做者簡介:
羅麗,ThoughtWorks 高級軟件工程師,高級軟件工程師,移動技術開發顧問,能熟練在項目中運用
TDD 及 Refactor 等技術,擁有豐富的軟件開發經驗, 多年海內外項目交付的經歷,擅長Android,iOS 等多平臺開發技術,目前任職於 ThoughtWorks 海外事業部,曾在多個大型移動應用架構中擔任技術顧問。