React Native系列之Recyclerlistview使用详解

 更新时间:2022年10月09日 10:46:45   作者:菜鸟正在搬砖  
这篇文章主要为大家介绍了React Native系列之Recyclerlistview使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

recyclerlistview的介绍与使用

1.安装

npm install --save recyclerlistview
或者:
yarn add recyclerlistview

2.概述和功能

RecyclerListView 是一个高性能的列表(listview)组件,同时支持 React NativeWeb ,并且可用于复杂的列表。RecyclerListView 组件的实现灵感,来自于 Android RecyclerView原生组件及iOS UICollectionView原生组件。

RecyclerListView使用“cell recycling”来重用不再可见的视图来呈现项目,而不是创建新的视图对象。 对象的创建非常昂贵并且带有内存开销,这意味着当您滚动列表时内存占用量不断增加。 从内存中释放不可见的项目是另一种技术,但这会导致创建更多的对象和大量的垃圾收集。 回收是渲染无限列表的最佳方式,不会影响性能或内存效率。

为什么需要RecyclerListView

我们知道,React Native 的其他列表组件如ListView,会一次性创建所有的列表单元格——cell。如果列表数据比较多,则会创建很多的视图对象,而视图对象是非常消耗内存的。所以,ListView组件,对于我们业务中的这种无限列表,基本上是不可以用的。

对于React Native 官方提供的高性能的列表组件FlatList, 在Android设备上的表现,并不是十分友好。它的实现原理,是将列表中不在可视区域内的视图,进行回收,然后根据页面的滚动,不断的渲染出现在可视区域内的视图。这里需要注意的是,FlatList是将不可见的视图回收,从内存中清除了,下次需要的时候,再重新创建。这就要求设备在滚动的时候,能快速的创建出需要的视图,才能让列表流畅的展现在用户面前。而问题也就出现在这里,Android设备因为老化等原因,计算力等跟不上,加之React Native 本身 JS 层与 Native 层之间交互的一些问题(这里不做深入追究),导致创建视图的速度达不到使列表流畅滚动的要求。

那怎样来解决这样的问题呢?

RecyclerListView 受到 Android RecyclerViewiOS UICollectionView 的启发,进行两方面的优化:

  • 仅创建可见区域的视图,这步与FlatList是一致的。
  • cell recycling,重用单元格,这个做法是FlatList缺乏的。

对于程序来说,视图对象的创建是非常昂贵的,并且伴随着内存的消耗。意味着如果不断的创建视图,在列表滚动的过程中,内存占用量会不断增加。FlatList中将不可见的视图从内存中移除,这是一个比较好的优化手段,但同时也会导致大量的视图重新创建以及垃圾回收。

RecyclerListView 通过对不可见视图对象进行缓存及重复利用,一方面不会创建大量的视图对象,另一方面也不需要频繁的创建视图对象和垃圾回收。

基于这样的理论,所以RecyclerListView的性能是会优于FlatList的。

3. RecyclerListView的使用

属性:

1、dataProvider

首先需要定义一个数据驱动方法

let dataProvider = new DataProvider((r1, r2) => {
    return r1 !== r2;
})

定义完成之后去初始化数据

// 列表数据
    const [JRecyclerData, setJRecyclerData] = useState(_dataProvider.cloneWithRows(data));

cloneWithRows

  • 想要更新列表的dataProvider数据也就是(DataSource)必须每次通过cloneWithRows这个来重新挂载datasource的值。
  • clone方法会自动提取新数据并进行逐行对比(使用rowHasChanged方法中的策略),这样列表就知道哪些行需要重新渲染了。

2、LayoutProvider

定义列表布局

在这之前我们可以根据我们的业务场景,规划处几类的布局,然后自定义每种布局的类型来区分。

//表示列表中会出现三种ui类型的item
const ViewTypes = {
    FULL: 0,
    HALF_LEFT: 1,
    HALF_RIGHT: 2
}

下面就可以来区分布局了

  • 为了进行cell-recycling,RecyclerListView要求对每个cell(通常也叫Item)定义一个type,根据type设置cell的dim.widthdim.height
//第一个函数是定义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负责渲染一个cell,同样是根据type来进行渲染:

_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"
    }
};

页面效果:

但是在实际的业务开发中肯定不会是这么简单的,一般都会用到分页,下拉刷新什么的,下面介绍几个比较常用的属性:

4、onEndReached

列表触底是触发,一般是用来做上拉加载更过数据的时候来使用的

<RecyclerListView
    layoutProvider={this._layoutProvider}
    dataProvider={this.dataProvider.cloneWithRows(this.state.infoList)}
    rowRenderer={this._rowRenderer}
    onEndReached={this._onLoadMore}
/>

5、onEndReachedThreshold

列表距离底部多大距离时触发onEndReached的回调,这个填写的是具体的像素值,与FlatList是有区别的,FlatList填写的是百分比

<RecyclerListView
    layoutProvider={this._layoutProvider}
    dataProvider={this.dataProvider.cloneWithRows(this.state.infoList)}
    rowRenderer={this._rowRenderer}
    onEndReached={this._onLoadMore}
    onEndReachedThreshold={50}
/>

6、extendedState

在更新目前列表渲染以外的数据时,可以使用此属性更新状态,以便绘制出新的列表,并且不再重新渲染以前的列表数据

<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使用详解的详细内容,更多关于React Native使用Recyclerlistview的资料请关注脚本之家其它相关文章!

相关文章

  • 详解React 如何防止 XSS 攻击论$$typeof 的作用

    详解React 如何防止 XSS 攻击论$$typeof 的作用

    这篇文章主要介绍了详解React 如何防止 XSS 攻击论$$typeof 的作用,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-07-07
  • 详解React中函数式组件与类组件的不同

    详解React中函数式组件与类组件的不同

    React 函数式组件与类组件的主要区别在于它们的定义和声明方式以及它们之间的一些特性,所以本文就详细的给大家讲讲React中函数式组件与类组件有何不同,需要的朋友可以参考下
    2023-09-09
  • react  Suspense工作原理解析

    react  Suspense工作原理解析

    这篇文章主要为大家介绍了react  Suspense工作原理解析以及基本应用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • React组件之多选Checkbox实例

    React组件之多选Checkbox实例

    这篇文章主要介绍了React组件之多选Checkbox实例,具有很好的参考价值,希望对大家有所帮助,
    2023-10-10
  • React函数组件和类组件的区别及说明

    React函数组件和类组件的区别及说明

    这篇文章主要介绍了React函数组件和类组件的区别及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 基于React Context实现一个简单的状态管理的示例代码

    基于React Context实现一个简单的状态管理的示例代码

    本文主要介绍了基于React Context实现一个简单的状态管理的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • 基于React-Dropzone开发上传组件功能(实例演示)

    基于React-Dropzone开发上传组件功能(实例演示)

    这篇文章主要介绍了基于React-Dropzone开发上传组件,主要讲述的是在React-Flask框架上开发上传组件的技巧,需要的朋友可以参考下
    2021-08-08
  • ReactNative实现Toast的示例

    ReactNative实现Toast的示例

    这篇文章主要介绍了ReactNative实现Toast的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • React-hooks中的useEffect使用步骤

    React-hooks中的useEffect使用步骤

    这篇文章主要介绍了React-hooks中的useEffect,对于React组件来说,主作用是根据数据(state/props)渲染UI,除此之外都是副作用(比如手动修改DOM、发送ajax请求),本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2022-05-05
  • React+TS+IntersectionObserver实现视频懒加载和自动播放功能

    React+TS+IntersectionObserver实现视频懒加载和自动播放功能

    通过本文的介绍,我们学习了如何使用 React + TypeScript 和 IntersectionObserver API 来实现一个视频播放控制组件,该组件具有懒加载功能,只有在用户滚动页面且视频进入视口时才开始下载视频资源,需要的朋友可以参考下
    2023-04-04

最新评论