React?Native系列之Recyclerlistview使用詳解
recyclerlistview的介紹與使用
1.安裝
npm install --save recyclerlistview 或者: yarn add recyclerlistview
2.概述和功能
RecyclerListView 是一個高性能的列表(listview)組件,同時支持 React Native 和 Web ,并且可用于復(fù)雜的列表。RecyclerListView 組件的實現(xiàn)靈感,來自于 Android RecyclerView原生組件及iOS UICollectionView原生組件。
RecyclerListView使用“cell recycling”來重用不再可見的視圖來呈現(xiàn)項目,而不是創(chuàng)建新的視圖對象。 對象的創(chuàng)建非常昂貴并且?guī)в袃?nèi)存開銷,這意味著當(dāng)您滾動列表時內(nèi)存占用量不斷增加。 從內(nèi)存中釋放不可見的項目是另一種技術(shù),但這會導(dǎo)致創(chuàng)建更多的對象和大量的垃圾收集。 回收是渲染無限列表的最佳方式,不會影響性能或內(nèi)存效率。
為什么需要RecyclerListView
我們知道,React Native 的其他列表組件如ListView,會一次性創(chuàng)建所有的列表單元格——cell。如果列表數(shù)據(jù)比較多,則會創(chuàng)建很多的視圖對象,而視圖對象是非常消耗內(nèi)存的。所以,ListView組件,對于我們業(yè)務(wù)中的這種無限列表,基本上是不可以用的。
對于React Native 官方提供的高性能的列表組件FlatList, 在Android設(shè)備上的表現(xiàn),并不是十分友好。它的實現(xiàn)原理,是將列表中不在可視區(qū)域內(nèi)的視圖,進(jìn)行回收,然后根據(jù)頁面的滾動,不斷的渲染出現(xiàn)在可視區(qū)域內(nèi)的視圖。這里需要注意的是,FlatList是將不可見的視圖回收,從內(nèi)存中清除了,下次需要的時候,再重新創(chuàng)建。這就要求設(shè)備在滾動的時候,能快速的創(chuàng)建出需要的視圖,才能讓列表流暢的展現(xiàn)在用戶面前。而問題也就出現(xiàn)在這里,Android設(shè)備因為老化等原因,計算力等跟不上,加之React Native 本身 JS 層與 Native 層之間交互的一些問題(這里不做深入追究),導(dǎo)致創(chuàng)建視圖的速度達(dá)不到使列表流暢滾動的要求。
那怎樣來解決這樣的問題呢?
RecyclerListView 受到 Android RecyclerView 和 iOS UICollectionView 的啟發(fā),進(jìn)行兩方面的優(yōu)化:
- 僅創(chuàng)建可見區(qū)域的視圖,這步與
FlatList是一致的。 cell recycling,重用單元格,這個做法是FlatList缺乏的。
對于程序來說,視圖對象的創(chuàng)建是非常昂貴的,并且伴隨著內(nèi)存的消耗。意味著如果不斷的創(chuàng)建視圖,在列表滾動的過程中,內(nèi)存占用量會不斷增加。FlatList中將不可見的視圖從內(nèi)存中移除,這是一個比較好的優(yōu)化手段,但同時也會導(dǎo)致大量的視圖重新創(chuàng)建以及垃圾回收。
RecyclerListView 通過對不可見視圖對象進(jìn)行緩存及重復(fù)利用,一方面不會創(chuàng)建大量的視圖對象,另一方面也不需要頻繁的創(chuàng)建視圖對象和垃圾回收。
基于這樣的理論,所以RecyclerListView的性能是會優(yōu)于FlatList的。
3. RecyclerListView的使用
屬性:
1、dataProvider
首先需要定義一個數(shù)據(jù)驅(qū)動方法
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這個來重新掛載datasource的值。
- clone方法會自動提取新數(shù)據(jù)并進(jìn)行逐行對比(使用rowHasChanged方法中的策略),這樣列表就知道哪些行需要重新渲染了。
2、LayoutProvider
定義列表布局
在這之前我們可以根據(jù)我們的業(yè)務(wù)場景,規(guī)劃處幾類的布局,然后自定義每種布局的類型來區(qū)分。
//表示列表中會出現(xiàn)三種ui類型的item
const ViewTypes = {
FULL: 0,
HALF_LEFT: 1,
HALF_RIGHT: 2
}
下面就可以來區(qū)分布局了
- 為了進(jìn)行
cell-recycling,RecyclerListView要求對每個cell(通常也叫Item)定義一個type,根據(jù)type設(shè)置cell的dim.width和dim.height:
//第一個函數(shù)是定義item的ui類型,第二個是定義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é)渲染一個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"
}
};
頁面效果:
但是在實際的業(yè)務(wù)開發(fā)中肯定不會是這么簡單的,一般都會用到分頁,下拉刷新什么的,下面介紹幾個比較常用的屬性:
4、onEndReached
列表觸底是觸發(fā),一般是用來做上拉加載更過數(shù)據(jù)的時候來使用的
<RecyclerListView
layoutProvider={this._layoutProvider}
dataProvider={this.dataProvider.cloneWithRows(this.state.infoList)}
rowRenderer={this._rowRenderer}
onEndReached={this._onLoadMore}
/>
5、onEndReachedThreshold
列表距離底部多大距離時觸發(fā)onEndReached的回調(diào),這個填寫的是具體的像素值,與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ù)時,可以使用此屬性更新狀態(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的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解React?如何防止?XSS?攻擊論$$typeof?的作用
這篇文章主要介紹了詳解React?如何防止?XSS?攻擊論$$typeof?的作用,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-07-07
基于React Context實現(xiàn)一個簡單的狀態(tài)管理的示例代碼
本文主要介紹了基于React Context實現(xiàn)一個簡單的狀態(tài)管理的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
基于React-Dropzone開發(fā)上傳組件功能(實例演示)
這篇文章主要介紹了基于React-Dropzone開發(fā)上傳組件,主要講述的是在React-Flask框架上開發(fā)上傳組件的技巧,需要的朋友可以參考下2021-08-08
React+TS+IntersectionObserver實現(xiàn)視頻懶加載和自動播放功能
通過本文的介紹,我們學(xué)習(xí)了如何使用 React + TypeScript 和 IntersectionObserver API 來實現(xiàn)一個視頻播放控制組件,該組件具有懶加載功能,只有在用戶滾動頁面且視頻進(jìn)入視口時才開始下載視頻資源,需要的朋友可以參考下2023-04-04

