React Native组件之VirtualizedList

React Native(简称RN)列表是基于ScrollView实现的,也就是能够滚动的,然而RN并无直接使用IOS或Android的原生列表组件,这是由于RN真正调用native代码的过程是异步的,二Native的渲染要求必须同步渲染的。node

在早期版本中,对于列表状况RN采用的是ListView组件,和Android同样,早期的ListView组件性能是很是的差的,在后来的版本中,RN提供了系列用于提升列表组件性能的组件:FlatList和SectionList。FlatList和SectionList都是基于VirtualizedList实现的。react

读者能够在项目的“node_modules/react-native/Libraries/Lists/XXX”文件夹下找到相关的源码。通常来讲,FlatList和SectionList已经可以知足常见的开发需求,仅当想得到比FlatList 更高的灵活性(好比说在使用 immutable data 而不是普通数组)的时候,才会应该考虑使用VirtualizedList。react-native

VirtualizedList

VirtualizedList经过维护一个有限的渲染窗口(其中包含可见的元素),并将渲染窗口以外的元素所有用合适的定长空白空间代替的方式,极大的改善了内存消耗以及在有大量数据状况下的使用性能(相似于Android的ListView的界面复用机制)。数组

当一个元素离可视区太远时,它的渲染的优先级较低,不然就得到一个较高的优先级,VirtualizedList经过这种机制来提升列表的渲染性能。在使用VirtualizedList赢注意如下几点:bash

  • 当某行滑出渲染区域以外后,其内部状态将不会保留,请确保在行组件之外的地方保留了数据。
  • 本组件继承自PureComponent而非一般的Component,这意味着若是其props在浅比较中是相等的,则不会从新渲染。因此请先检查你的renderItem函数所依赖的props数据(包括data属性以及可能用到的父组件的state),若是是一个引用类型(Object或者数组都是引用类型),则须要先修改其引用地址(好比先复制到一个新的Object或者数组中),而后再修改其值,不然界面极可能不会刷新。
  • 为了优化内存占用同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着若是用户滑动的速度超过渲染的速度,则会先看到空白的内容。
  • 默认状况下每行都须要提供一个不重复的key属性,开发者能够提供一个keyExtractor函数来生成key(否则会给出黄色的警告信息)。

##属性 因为VirtualizedList是FlatList和SectionList的父组件,因此VirtualizedList提供的属性,FlatList和SectionList都可以找到。异步

  • data?: any 默认的函数获取器,假设它是一个数组类型(Array<{key: string}>),可是能够经过重写getItem、getItemCount、keyExtractor 来处理任何类型的可索引数据。
  • debug?: ?boolean 开启额外的日志和视觉覆盖功能来协助调试,可是开启会影响性能。
  • disableVirtualization: boolean 已过期: Virtualization 提供了显著的性能和内存优化,而且彻底卸载了位于可视区以外的 react 实例。
  • extraData?: any 标记属性,用来告诉列表须要从新渲染(实现了PureComponent)。若是有 data 属性以外的数据引用,就须要把它列在这里,并把它当成不可变的。
  • getItem: (data: any, index: number) => ?Item 通用的获取器,用来从任意类型的数据块中获取一个元素。
  • getItemCount: (data: any) => number 用来决定数据块中一共有多少元素。
  • getItemLayout?: (data: any, index: number) getItemLayout是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你能够提早知道内容的高度。例如,若是行高是固定的,那么getItemLayout用起来就既高效又简单。
  • horizontal?: ?boolean 设置为true则变为水平布局模式。
  • initialNumToRender: number 首批应该渲染的元素数量。注意:为了响应“滚动到顶部”这个事件并最优化其性能,这些元素将做为窗口渲染的一部分,永远不会被卸载。
  • keyExtractor: (item: Item, index: number) 此函数用于为给定的item生成一个不重复的key。Key的做用是使React可以区分同类元素的不一样个体,以便在刷新时可以肯定其变化的位置,减小从新渲染的开销。
  • maxToRenderPerBatch: number 每批增量渲染可渲染的最大数量。能当即渲染出的元素数量越多,填充速率就越快,可是响应性可能会有一些损失,由于每一个被渲染的元素均可能参与或干扰对按钮点击事件或其余事件的响应。
  • onEndReached?: ?(info: {distanceFromEnd: number}) 当列表被滚动到距离内容最底部不足 onEndReachedThreshold 的距离时调用。
  • onEndReachedThreshold?: ?number 决定当距离内容最底部还有多远时触发 onEndReached 回调,注意此参数是一个比值而非像素单位
  • onLayout?: ?Function 当组件挂载或者布局变化的时候调用,同View的onLayout。
  • onRefresh?: ?Function 若是设置了此选项,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。
  • onViewableItemsChanged 当列表中行的可见性发生变化时,就会调用这个函数。可见性设置见viewabilityConfig。
  • refreshing?: ?boolean 当等待数据进行更新时,将这个属性设置为true。
  • removeClippedSubviews?: boolean 一个将“剪裁子视图”(clipped subviews)从视图层级中删除的本地优化,为的是减轻渲染系统的工做负担。可是这些被剪裁掉的子视图依然保留在内存中,因此它们所占的储存空间没有被释放,内部状态也都保留了下来。
  • renderItem: (info: {item: Item, index: number}) 根据行数据data渲染每一行的组件视图。
  • renderScrollComponent: (props: Object) 渲染一个自定义的滚动组件,好比说这个组件有一种不一样的刷新控制方式。
  • updateCellsBatchingPeriod: number 具备较低渲染优先级的元素(好比那些离屏幕至关远的元素)的渲染批次之间的时间间隔。与maxToRenderPerBatch具备相同的目的,都是为了在渲染速率和响应性之间得到一个平衡。
  • windowSize: number 设置可视区外最大能被渲染的元素的数量,以可视区的长度为单位。将windowSize设置为一个较小值,能有减少内存消耗并提升性能,可是当你快速滚动列表时,遇到还没有渲染的内容的概率会增大,而这些还没有渲染的内容会暂时性地被空白区块所替代。

源码分析

VirtualizedList执行的流程以下:函数

  • 每次新增绘制item的最大数量为10,循环绘制(默认以10为单位累加绘制);
  • 首先绘制显示在屏幕中的items,再根据优先级循环绘制屏幕上显示items相近的数据,直至绘制完成;
  • 每次绘制过程当中,全部不须要绘制的元素用空View代替;

##循环绘制 循环绘制的加载方法为_scheduleCellsToRenderUpdate()。源码分析

componentDidUpdate() {
        this._scheduleCellsToRenderUpdate();
    }
复制代码

在每次刷新完成后会调用_scheduleCellsToRenderUpdate方法,该方法最终会调用_updateCellsToRender方法。布局

_updateCellsToRender = () => {
        const {data, getItemCount, onEndReachedThreshold} = this.props;
        const isVirtualizationDisabled = this._isVirtualizationDisabled();
        this._updateViewableItems(data);
        if (!data) {
            return;
        }
        this.setState(state => {
            let newState;
            if (!isVirtualizationDisabled) {
                // If we run this with bogus data, we'll force-render window {first: 0, last: 0}, // and wipe out the initialNumToRender rendered elements. // So let's wait until the scroll view metrics have been set up. And until then,
                // we will trust the initialNumToRender suggestion
                if (this._scrollMetrics.visibleLength) {
                    // If we have a non-zero initialScrollIndex and run this before we've scrolled, // we'll wipe out the initialNumToRender rendered elements starting at initialScrollIndex.
                    // So let's wait until we've scrolled the view to the right place. And until then,
                    // we will trust the initialScrollIndex suggestion.
                    if (!this.props.initialScrollIndex || this._scrollMetrics.offset) {
                        newState = computeWindowedRenderLimits(
                            this.props,
                            state,
                            this._getFrameMetricsApprox,
                            this._scrollMetrics,
                        );
                    }
                }
            } else {
                const {contentLength, offset, visibleLength} = this._scrollMetrics;
                const distanceFromEnd = contentLength - visibleLength - offset;
                const renderAhead =
                    distanceFromEnd < onEndReachedThreshold * visibleLength
                        ? this.props.maxToRenderPerBatch
                        : 0;
                newState = {
                    first: 0,
                    last: Math.min(state.last + renderAhead, getItemCount(data) - 1),
                };
            }
            return newState;
        });
    };
复制代码

在_updateCellsToRender中会调用setState方法更新状态。因此在每次绘制完成(状态更新完成)后,都会接着调用更新方法,因此造成了循环绘制的效果。理论上这种结构会形成无限循环,可是VirtualizedList是继承自PureComponent,因此当检测到状态未改变的时候就会终止更新。性能

在上述_updateCellsToRender方法中,调用了computeWindowedRenderLimits生成最新的first、last,该方法属于VirtualizeUtils类。它是根据优先级动态计算first和last,距离屏幕显示组件的数据越近,优先级越高。

相关文章
相关标签/搜索