React?Native系列之Recyclerlistview使用詳解
recyclerlistview的介紹與使用
1.安裝
npm install --save recyclerlistview 或者: yarn add recyclerlistview
2.概述和功能
RecyclerListView 是一個(gè)高性能的列表(listview)組件,同時(shí)支持 React Native
和 Web
,并且可用于復(fù)雜的列表。RecyclerListView 組件的實(shí)現(xiàn)靈感,來自于 Android RecyclerView
原生組件及iOS UICollectionView
原生組件。
RecyclerListView使用“cell recycling”來重用不再可見的視圖來呈現(xiàn)項(xiàng)目,而不是創(chuàng)建新的視圖對(duì)象。 對(duì)象的創(chuàng)建非常昂貴并且?guī)в袃?nèi)存開銷,這意味著當(dāng)您滾動(dòng)列表時(shí)內(nèi)存占用量不斷增加。 從內(nèi)存中釋放不可見的項(xiàng)目是另一種技術(shù),但這會(huì)導(dǎo)致創(chuàng)建更多的對(duì)象和大量的垃圾收集。 回收是渲染無限列表的最佳方式,不會(huì)影響性能或內(nèi)存效率。
為什么需要RecyclerListView
我們知道,React Native
的其他列表組件如ListView
,會(huì)一次性創(chuàng)建所有的列表單元格——cell。如果列表數(shù)據(jù)比較多,則會(huì)創(chuàng)建很多的視圖對(duì)象,而視圖對(duì)象是非常消耗內(nèi)存的。所以,ListView
組件,對(duì)于我們業(yè)務(wù)中的這種無限列表,基本上是不可以用的。
對(duì)于React Native
官方提供的高性能的列表組件FlatList
, 在Android設(shè)備上的表現(xiàn),并不是十分友好。它的實(shí)現(xiàn)原理,是將列表中不在可視區(qū)域內(nèi)的視圖,進(jìn)行回收,然后根據(jù)頁面的滾動(dòng),不斷的渲染出現(xiàn)在可視區(qū)域內(nèi)的視圖。這里需要注意的是,FlatList
是將不可見的視圖回收,從內(nèi)存中清除了,下次需要的時(shí)候,再重新創(chuàng)建。這就要求設(shè)備在滾動(dòng)的時(shí)候,能快速的創(chuàng)建出需要的視圖,才能讓列表流暢的展現(xiàn)在用戶面前。而問題也就出現(xiàn)在這里,Android設(shè)備因?yàn)槔匣仍?,?jì)算力等跟不上,加之React Native
本身 JS 層與 Native 層之間交互的一些問題(這里不做深入追究),導(dǎo)致創(chuàng)建視圖的速度達(dá)不到使列表流暢滾動(dòng)的要求。
那怎樣來解決這樣的問題呢?
RecyclerListView
受到 Android RecyclerView
和 iOS UICollectionView
的啟發(fā),進(jìn)行兩方面的優(yōu)化:
- 僅創(chuàng)建可見區(qū)域的視圖,這步與
FlatList
是一致的。 cell recycling
,重用單元格,這個(gè)做法是FlatList
缺乏的。
對(duì)于程序來說,視圖對(duì)象的創(chuàng)建是非常昂貴的,并且伴隨著內(nèi)存的消耗。意味著如果不斷的創(chuàng)建視圖,在列表滾動(dòng)的過程中,內(nèi)存占用量會(huì)不斷增加。FlatList
中將不可見的視圖從內(nèi)存中移除,這是一個(gè)比較好的優(yōu)化手段,但同時(shí)也會(huì)導(dǎo)致大量的視圖重新創(chuàng)建以及垃圾回收。
RecyclerListView
通過對(duì)不可見視圖對(duì)象進(jìn)行緩存及重復(fù)利用,一方面不會(huì)創(chuàng)建大量的視圖對(duì)象,另一方面也不需要頻繁的創(chuàng)建視圖對(duì)象和垃圾回收。
基于這樣的理論,所以RecyclerListView
的性能是會(huì)優(yōu)于FlatLis
t的。
3. RecyclerListView的使用
屬性:
1、dataProvider
首先需要定義一個(gè)數(shù)據(jù)驅(qū)動(dòng)方法
let dataProvider = new DataProvider((r1, r2) => { return r1 !== r2; })
定義完成之后去初始化數(shù)據(jù)
// 列表數(shù)據(jù) const [JRecyclerData, setJRecyclerData] = useState(_dataProvider.cloneWithRows(data));
cloneWithRows
- 想要更新列表的dataProvider數(shù)據(jù)也就是(DataSource)必須每次通過cloneWithRows這個(gè)來重新掛載datasource的值。
- clone方法會(huì)自動(dòng)提取新數(shù)據(jù)并進(jìn)行逐行對(duì)比(使用rowHasChanged方法中的策略),這樣列表就知道哪些行需要重新渲染了。
2、LayoutProvider
定義列表布局
在這之前我們可以根據(jù)我們的業(yè)務(wù)場(chǎng)景,規(guī)劃處幾類的布局,然后自定義每種布局的類型來區(qū)分。
//表示列表中會(huì)出現(xiàn)三種ui類型的item const ViewTypes = { FULL: 0, HALF_LEFT: 1, HALF_RIGHT: 2 }
下面就可以來區(qū)分布局了
- 為了進(jìn)行
cell-recycling
,RecyclerListView
要求對(duì)每個(gè)cell(通常也叫Item)定義一個(gè)type,根據(jù)type設(shè)置cell的dim.width
和dim.height
:
//第一個(gè)函數(shù)是定義item的ui類型,第二個(gè)是定義item的高寬 this._layoutProvider = new LayoutProvider( index => { if (index % 3 === 0) { return ViewTypes.FULL; } ... }, (type, dim) => { switch (type) { case ViewTypes.HALF_LEFT: dim.width = width / 2; dim.height = 160; break; ... } } )
3、rowRenderer
rowRenderer負(fù)責(zé)渲染一個(gè)cell,同樣是根據(jù)type來進(jìn)行渲染:
_rowRenderer(type, data) { switch (type) { case ViewTypes.HALF_LEFT: return ( <CellContainer style={styles.containerGridLeft}> <Text>Data: {data}</Text> </CellContainer> ); ... } }
例子:
/*** * To test out just copy this component and render in you root component */ export default class RecycleTestComponent extends React.Component { constructor(args) { super(args); let { width } = Dimensions.get("window"); //Create the data provider and provide method which takes in two rows of data and return if those two are different or not. let dataProvider = new DataProvider((r1, r2) => { return r1 !== r2; }); //Create the layout provider //First method: Given an index return the type of item e.g ListItemType1, ListItemType2 in case you have variety of items in your list/grid //Second: Given a type and object set the height and width for that type on given object //If you need data based check you can access your data provider here //You'll need data in most cases, we don't provide it by default to enable things like data virtualization in the future //NOTE: For complex lists LayoutProvider will also be complex it would then make sense to move it to a different file this._layoutProvider = new LayoutProvider( index => { if (index % 3 === 0) { return ViewTypes.FULL; } else if (index % 3 === 1) { return ViewTypes.HALF_LEFT; } else { return ViewTypes.HALF_RIGHT; } }, (type, dim) => { switch (type) { case ViewTypes.HALF_LEFT: dim.width = width / 2 - 0.0001; dim.height = 160; break; case ViewTypes.HALF_RIGHT: dim.width = width / 2; dim.height = 160; break; case ViewTypes.FULL: dim.width = width; dim.height = 140; break; default: dim.width = 0; dim.height = 0; } } ); this._rowRenderer = this._rowRenderer.bind(this); //Since component should always render once data has changed, make data provider part of the state this.state = { dataProvider: dataProvider.cloneWithRows(this._generateArray(300)) }; } _generateArray(n) { let arr = new Array(n); for (let i = 0; i < n; i++) { arr[i] = i; } return arr; } //Given type and data return the view component _rowRenderer(type, data) { //You can return any view here, CellContainer has no special significance switch (type) { case ViewTypes.HALF_LEFT: return ( <CellContainer style={styles.containerGridLeft}> <Text>Data: {data}</Text> </CellContainer> ); case ViewTypes.HALF_RIGHT: return ( <CellContainer style={styles.containerGridRight}> <Text>Data: {data}</Text> </CellContainer> ); case ViewTypes.FULL: return ( <CellContainer style={styles.container}> <Text>Data: {data}</Text> </CellContainer> ); default: return null; } } render() { return <RecyclerListView layoutProvider={this._layoutProvider} dataProvider={this.state.dataProvider} rowRenderer={this._rowRenderer} />; } } const styles = { container: { justifyContent: "space-around", alignItems: "center", flex: 1, backgroundColor: "#00a1f1" }, containerGridLeft: { justifyContent: "space-around", alignItems: "center", flex: 1, backgroundColor: "#ffbb00" }, containerGridRight: { justifyContent: "space-around", alignItems: "center", flex: 1, backgroundColor: "#7cbb00" } };
頁面效果:
但是在實(shí)際的業(yè)務(wù)開發(fā)中肯定不會(huì)是這么簡(jiǎn)單的,一般都會(huì)用到分頁,下拉刷新什么的,下面介紹幾個(gè)比較常用的屬性:
4、onEndReached
列表觸底是觸發(fā),一般是用來做上拉加載更過數(shù)據(jù)的時(shí)候來使用的
<RecyclerListView layoutProvider={this._layoutProvider} dataProvider={this.dataProvider.cloneWithRows(this.state.infoList)} rowRenderer={this._rowRenderer} onEndReached={this._onLoadMore} />
5、onEndReachedThreshold
列表距離底部多大距離時(shí)觸發(fā)onEndReached的回調(diào),這個(gè)填寫的是具體的像素值,與FlatList
是有區(qū)別的,FlatList
填寫的是百分比
<RecyclerListView layoutProvider={this._layoutProvider} dataProvider={this.dataProvider.cloneWithRows(this.state.infoList)} rowRenderer={this._rowRenderer} onEndReached={this._onLoadMore} onEndReachedThreshold={50} />
6、extendedState
在更新目前列表渲染以外的數(shù)據(jù)時(shí),可以使用此屬性更新狀態(tài),以便繪制出新的列表,并且不再重新渲染以前的列表數(shù)據(jù)
<RecyclerListView layoutProvider={this._layoutProvider} dataProvider={this.dataProvider.cloneWithRows(this.state.infoList)} rowRenderer={this._rowRenderer} onEndReached={this._onLoadMore} onEndReachedThreshold={50} extendedState={this.state} />
7、scrollViewProps
繼承scrollView的屬性,RecyclerListView本身是不具有刷新屬性的,要想使用刷新功能,就可以繼承scrollView的下拉刷新
<RecyclerListView scrollViewProps={{ refreshControl: ( <RefreshControl refreshing={this.state.loading} onRefresh={async () => { this.setState({ loading: true }); await this.getInfo(); this.setState({ loading: false }); }} /> ) }} />
下面看一下完整的例子:
import React, { Component } from "react"; import { View, Text, Dimensions, StyleSheet, RefreshControl, Alert } from "react-native"; import { RecyclerListView, DataProvider, LayoutProvider } from "recyclerlistview"; import WBCST from "./../../rn-app"; const ViewTypes = { FULL: 0 }; const { width } = Dimensions.get("window"); const styles = StyleSheet.create({ container: { flexDirection: "row", justifyContent: "space-between", // alignItems: "center", flex: 1, backgroundColor: "#fff", // borderWidth: 1, borderColor: "#dddddd", margin: 15, marginTop: 0, padding: 15 }, topicLeft: { width: width - 210, marginRight: 10 }, topicRight: { backgroundColor: "#f5f5f5", width: 140, height: 140, padding: 15 }, topicTitle: { color: "#000", fontSize: 16, fontWeight: "700", lineHeight: 28 }, topicContext: { color: "#999", fontSize: 12, lineHeight: 18, marginTop: 10 }, topicNum: { fontSize: 14, marginTop: 20 }, topicRightText: { fontSize: 14, color: "#666" } }); export default class RecycleTestComponent extends Component { constructor(props) { super(props); this.dataProvider = new DataProvider((r1, r2) => { return r1 !== r2; }); let { width } = Dimensions.get("window"); this._layoutProvider = new LayoutProvider( (index) => { return ViewTypes.FULL; }, (type, dim) => { dim.width = width; dim.height = 190; } ); this.state = { pagenum: 1, infoList: [], loading: false, isLoadMore: false }; } getInfo = () => { let num = this.state.pagenum; let info = this.state.infoList; WBCST.getFetch("http://app.58.com/api/community/aggregatepage/tabs/topic", { pagesize: 20, pagenum: num }).then((res) => { if (res) { let loadMore = false; if (num == 1) { if (res.data.questions.length == 20) { loadMore = true; } this.setState({ isLoadMore: loadMore, infoList: res.data.questions }); } else { // info.concat(res.data.questions); if (res.data.questions.length < 20) { loadMore = false; } else { loadMore = true; } this.setState({ isLoadMore: loadMore, infoList: this.state.infoList.concat(res.data.questions) }); } } }); }; _rowRenderer = (type, data) => { return ( <View style={styles.container}> <View style={styles.topicLeft}> <Text numberOfLines={2} style={styles.topicTitle}> {data.topic.title} </Text> <Text numberOfLines={2} style={styles.topicContext}> {data.topic.context} </Text> <Text style={styles.topicNum}> {data.topic.pn} 人參與此話題 </Text> </View> <View style={styles.topicRight}> <Text style={styles.topicRightText}>{data.user.name}</Text> <Text style={[{ marginTop: 10 }, styles.topicRightText]}>{data.title}</Text> </View> </View> ); }; _renderFooter = () => { return ( <View> <Text>上拉加載更多</Text> </View> ); }; _onLoadMore = () => { // Alert.alert(JSON.stringify("num")); if (!this.state.isLoadMore) { return; } let num = this.state.pagenum; num = num + 1; this.setState( { pagenum: num }, () => { // Alert.alert(JSON.stringify(num)); this.getInfo(); } ); }; componentDidMount = () => { this.getInfo(); }; render() { return ( <RecyclerListView layoutProvider={this._layoutProvider} dataProvider={this.dataProvider.cloneWithRows(this.state.infoList)} rowRenderer={this._rowRenderer} extendedState={this.state} onEndReached={this._onLoadMore} onEndReachedThreshold={50} // renderFooter={this._renderFooter} scrollViewProps={{ refreshControl: ( <RefreshControl refreshing={this.state.loading} onRefresh={async () => { this.setState({ loading: true }); // analytics.logEvent("Event_Stagg_pull_to_refresh"); await this.getInfo(); this.setState({ loading: false }); }} /> ) }} /> ); } }
效果圖:
RecyclerListView所有屬性
以上就是React Native系列之Recyclerlistview使用詳解的詳細(xì)內(nèi)容,更多關(guān)于React Native使用Recyclerlistview的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解React?如何防止?XSS?攻擊論$$typeof?的作用
這篇文章主要介紹了詳解React?如何防止?XSS?攻擊論$$typeof?的作用,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07基于React Context實(shí)現(xiàn)一個(gè)簡(jiǎn)單的狀態(tài)管理的示例代碼
本文主要介紹了基于React Context實(shí)現(xiàn)一個(gè)簡(jiǎn)單的狀態(tài)管理的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07基于React-Dropzone開發(fā)上傳組件功能(實(shí)例演示)
這篇文章主要介紹了基于React-Dropzone開發(fā)上傳組件,主要講述的是在React-Flask框架上開發(fā)上傳組件的技巧,需要的朋友可以參考下2021-08-08ReactNative實(shí)現(xiàn)Toast的示例
這篇文章主要介紹了ReactNative實(shí)現(xiàn)Toast的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12React+TS+IntersectionObserver實(shí)現(xiàn)視頻懶加載和自動(dòng)播放功能
通過本文的介紹,我們學(xué)習(xí)了如何使用 React + TypeScript 和 IntersectionObserver API 來實(shí)現(xiàn)一個(gè)視頻播放控制組件,該組件具有懶加載功能,只有在用戶滾動(dòng)頁面且視頻進(jìn)入視口時(shí)才開始下載視頻資源,需要的朋友可以參考下2023-04-04