React N...

React Native之FlatList组件(一)

2022-03-07  本文已影响0人  爱吃豆包

FlatList的由来?

在React Native0.43版本中引入了FlatList,SectionListVirtualizedList,其中VirtualizedList是FlatList 与 SectionList 的底层实现。

FlatList

可能有人要问了,既然有了ListView,那为什么还要设计一个FlatList出来呢?

经常使用ListView的同学都知道: ListView的性能是比较差的,尤其是当有大量的数据需要展示的时候,ListView对内存的占用是相当可观的、丢帧卡顿那是常有的事。

为什么ListView对于大数据量的情况下性能会很差呢?

深入ListView的原理你会发现,ListView对列表中的Item是全量渲染的,并且没有复用机制,这就难以避免当让ListView渲染大数据量的时候会发生以下两个问题:

ListView的这种性能问题一直困扰着React Native开发者。有能力的公司、团队都纷纷对ListView做优化,封装自己的列表组件,然对性能的提升并不大,所以现在急需一个高性能的列表组件,于是便有了设计FlatList的构想;

那FlatList都有哪些特性能呢?

FlatList是基于VirtualizedList的,要说FlatList的特性还要从VirtualizedList说起:

VirtualizedList

VirtualizedList 是FlatList 与 SectionList 的底层实现。Vritualization 通过维护一个有限的渲染窗口(其中包含可见的元素),并将渲染窗口之外的元素全部用合适的定长空白空间代替的方式,极大的改善了内存消耗以及在有大量数据情况下的使用性能。这个渲染窗口能响应滚动行为。当一个元素离可视区太远时,它就有一个较低优先级;否则就获得一个较高的优先级。渲染窗口通过这种方式逐步渲染其中的元素(在进行了任何交互之后),以尽量减少出现空白区域的可能性。

render-window

特性

VirtualizedList有以下特性:

性能

VirtualizedList除了简化API之外,新的列表组件还具有显着的性能增强,主要的是对于任意数量的行(Item)的增加不会带着内存的增加。 它主要是通过虚拟元素也就是在渲染窗口之外的元素将会被从组件结构上卸载以达到回收内存目的。这样会带来一个问题,即内部组件状态不会被保留,因此请确保你跟踪组件本身以外的任何重要状态,例如, 在Relay或Redux或Flux store。

限制渲染窗口还可以减少React和本地平台的工作量,例如View遍历。 即使你渲染了最后的一百万个元素,用这些新的列表也不需要渲染所有的元素来完成遍历。比如:你可以使用scrollToIndex跳至中间位置,而无需过多渲染。

另外VirtualizedList还对调度进行了一些改进,这对应用程序的响应很有帮助。 在任何手势或动画或其他交互完成后,呈现在窗口边缘的Item不会被频繁的渲染,并且渲染优先级比较低。

高级使用

注意事项

React Native列表的未来规划

了解完VirtualizedList之后,接下来就让我们来认识一下FlatList的一些特性吧:

FlatList的特性

高性能的且使用简单的列表组件,支持一些特性:

如果需要分组/类/区(section)的功能,请使用<SectionList>

简单使用

<FlatList
  data={[{key: 'a'}, {key: 'b'}]}
  renderItem={({item}) => <Text>{item.key}</Text>} />

查看全部完整代码

注意事项

FlatList组件实质是基于<VirtualizedList>组件的封装,因此除了<VirtualizedList>需要注意的事项之外还有下面这些需要注意的事项:

属性

data: ?Array<ItemT>

为了简化起见,data属性目前只支持普通数组。如果需要使用其他特殊数据结构,例如immutable数组,请直接使用更底层的VirtualizedList组件。

renderItem: (info: {item: ItemT, index: number}) => ?React.Element<any>

根据行数据data渲染每一行的组件。典型用法:

_renderItem = ({item}) => ( <TouchableOpacity onPress={() => this._onPress(item)}> <Text>{item.title}}</Text> </TouchableOpacity> ); ... <FlatList data={[{title: 'Title Text', key: 'item1'}]} renderItem={this._renderItem} />

查看全部完整代码

除data外还有第二个参数index可供使用。

onRefresh?: ?() => void

如果设置了此选项,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性。

refreshing?: ?boolean

在等待加载新数据时将此属性设为true,列表就会显示出一个正在加载的符号。

horizontal?: ?boolean

设置为true则变为水平布局模式。

initialNumToRender: number

指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素。

keyExtractor: (item: ItemT, index: number) => string

此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key作为key值。若item.key也不存在,则使用数组下标。

ItemSeparatorComponent?: ?ReactClass<any>

行与行之间的分隔线组件。不会出现在第一行之前和最后一行之后。�

ListFooterComponent?: ?ReactClass<any>

通过它设置尾部组件

ListHeaderComponent?: ?ReactClass<any>

通过它设置头部组件

columnWrapperStyle?: StyleObj

如果设置了多列布局(即将numColumns值设为大于1的整数),则可以额外指定此样式作用在每行容器上。

extraData?: any

如果有除data以外的数据用在列表中(不论是用在renderItem还是Header或者Footer中),请在此属性中指定。同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。

getItem?:

获取指定的Item;

getItemCount?:

用于获取总共有多少Item;

getItemLayout?: (data: ?Array<ItemT>, index: number) => {length: number, offset: number, index: number}

getItemLayout是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度。如果你的行高是固定的,getItemLayout用起来就既高效又简单,类似下面这样:

getItemLayout={(data, index) => ( {length: 行高, offset: 行高 * index, index} )}

注意如果你指定了SeparatorComponent,请把分隔线的尺寸也考虑到offset的计算之中。

legacyImplementation?: ?boolean

设置为true则使用旧的ListView的实现。

numColumns: number

多列布局只能在非水平模式下使用,即必须是horizontal={false}。此时组件内元素会从左到右从上到下按Z字形排列,类似启用了flexWrap的布局。组件内元素必须是等高的——暂时还无法支持瀑布流布局。

onEndReached?: ?(info: {distanceFromEnd: number}) => void

当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用。

onEndReachedThreshold?: ?number

决定当距离内容最底部还有多远时触发onEndReached回调。注意此参数是一个比值而非像素单位。比如,0.5表示距离内容最底部的距离为当前列表可见长度的一半时触发。

onViewableItemsChanged?: ?(info: {viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void

在可见行元素变化时调用。可见范围和变化频率等参数的配置请设置viewabilityconfig属性

viewabilityConfig?: ViewabilityConfig

可参考ViewabilityHelper的源码来了解具体的配置。

方法

scrollToEnd(params?: object)

滚动到底部。如果不设置getItemLayout属性的话,可能会比较卡。

scrollToIndex(params: object)

滚动到指定位置,如果不设置getItemLayout属性的话,可能会比较卡。

scrollToItem(params: object)

需要线性扫描数据 - 如果可能,请使用scrollToIndex。如果不设置getItemLayout属性的话只能滚动到当前渲染窗口的某个位置。

scrollToOffset(params: object)

滚动到列表中的特定内容像素偏移量。

recordInteraction()

复杂使用

下面是一个较复杂的例子,其中演示了如何利用PureComponent来进一步优化性能和减少bug产生的可能:

class MyListItem extends React.PureComponent {
  _onPress = () => {
    this.props.onPressItem(this.props.id);
  };

  render() {
    retuReact Native (
      <SomeOtherWidget
        {...this.props}
        onPress={this._onPress}
      />     )
  }
}

class MyList extends React.PureComponent { 
  state = {selected: (new Map(): Map<string, boolean>)};

  _keyExtractor = (item, index) => item.id;

  _onPressItem = (id: string) => {
    // updater functions are preferred for transactional updates
    this.setState((state) => {
      // copy the map rather than modifying state.
      const selected = new Map(state.selected);
      selected.set(id, !selected.get(id)); // toggle
      retuReact Native {selected};
    });
  };

  _renderItem = ({item}) => (
    <MyListItem
      id={item.id}
      onPressItem={this._onPressItem}
      selected={!!this.state.selected.get(item.id)}
      title={item.title}
    />   );

  render() {
    retuReact Native (
      <FlatList
        data={this.props.data}
        extraData={this.state}
        keyExtractor={this._keyExtractor}
        renderItem={this._renderItem}
      />     );
  }
}

实例:上拉加载更多,下拉刷新,自定义刷新组件

FlatListDemo
const CITY_NAMES = ['北京', '上海', '广州', '深圳', '杭州', '苏州', '成都', '武汉', '郑州', '洛阳', '厦门', '青岛', '拉萨'];
export default class FlatListDemo extends Component<Props> {
    constructor(props) {
        super(props);
        this.state = {
            dataArray: CITY_NAMES,
            isLoading: false
        }
    }

    _renderItem(data) {
        retuReact Native <View style={styles.item}>
            <Text style={styles.text}>{data.item}</Text>         </View>     }

    loadData(refresh) {
        if (refresh) {
            this.setState({
                isLoading: true
            });
        }
        setTimeout(() => {
            let dataArray = [];
            if (refresh) {
                for (let i = this.state.dataArray.length - 1; i >= 0; i--) {
                    dataArray.push(this.state.dataArray[i])
                }
            } else {
                dataArray = this.state.dataArray.concat(CITY_NAMES);
            }
            this.setState({
                dataArray: dataArray,
                isLoading: false
            });
        }, 2000);
    }

    genIndicator() {
        retuReact Native <View style={styles.indicatorContainer}>
            <ActivityIndicator
                style={styles.indicator}
                size='large'
                animating={true}
            />             <Text>正在加载更多</Text>         </View>     }

    render() {
        retuReact Native (
            <View style={styles.container}>
                <FlatList
                    data={this.state.dataArray}
                    renderItem={(data => this._renderItem(data))}
                    // refreshing={this.state.isLoading}
                    // onRefresh={() => {
                    // this.loadData();
                    // }}
                    refreshControl={
                        <RefreshControl
                            title='Loading...'
                            colors={['red']}
                            refreshing={this.state.isLoading}
                            onRefresh={() => this.loadData(true)}
                            tintColor={'orange'}
                        />                     }
                    ListFooterComponent={() => this.genIndicator()}
                    onEndReached={() => {
                        this.loadData()
                    }}
                /> 
            </View>         );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
    item: {
        height: 200,
        backgroundColor: '#169',
        marginLeft: 15,
        marginRight: 15,
        marginBottom: 15,
        alignItems: 'center',
        justifyContent: 'center'
    },
    text: {
        color: 'white',
        fontSize: 20
    },
    indicatorContainer: {
        alignItems: "center"
    },
    indicator: {
        color: 'red',
        margin: 10
    }
});

上一篇下一篇

猜你喜欢

热点阅读