In RN, FlatList is a high performance list component. It is the upgraded version of ListView component, which has a great improvement in performance. Of course, it is recommended that you use FlatList when implementing list functions, and try not to use ListView, not to use ScrollView. Speaking of FlatList, let’s review the features it supports.

  • Fully cross-platform.

  • Horizontal layout mode is supported.

  • The callback event can be configured when the row component is displayed or hidden.

  • Support for separate header components.

  • Support for separate tail components.

  • Supports custom line separators.

  • Supports drop-down refresh.

  • Supports pull-up loading.

  • Supports jumping to a specified row (ScrollToIndex).

This article introduces how to use no today, if you want to see how to use, you can refer to me making some examples of https://github.com/xiehui999/helloReactNative. Today’s article mainly introduces the secondary encapsulation of FlatList when I use the pit which feels relatively large. Let’s start with a simple example. Our article also has this example to begin to discuss.

        <FlatList
            data={this.state.dataList} extraData={this.state}
            refreshing={this.state.isRefreshing}
            onRefresh={() => this._onRefresh()}
            keyExtractor={(item, index) => item.id}
            ItemSeparatorComponent={() => <View style={{
                height: 1,
                backgroundColor: '#D6D6D6'RenderItem ={this._renderItem} ListEmptyComponent={this.emptyComponent}/> // Define empty layout emptyComponent = () => {return <View style={{
            height: '100%',
            alignItems: 'center',
            justifyContent: 'center',}} > < Text style = {{fontSize: 16}} > temporarily countless according to drop-down refresh < / Text > < View >}Copy the code

In the code above, we’ll focus on the ListEmptyComponent, which represents the layout to populate with no data. Normally, we’ll display a prompt in the middle, but simply show a no-data drop-down refresh for introduction purposes. The code above appears to have no data center display, but after running, you are surprised, no data center display at the top of the height of 100% has no effect. Of course you tried using Flex :1 to fill the rest of the full screen with the high View of the View, but it still didn’t work.

Then why did not set the effect, since curious, we will come and see the source code. The source path is react-native–>Libraries–>Lists. The list of components is in this directory. We search the FlatList file for the keyword ListEmptyComponent and find that the component is not used, so continue to render


  render() {
    if (this.props.legacyImplementation) {
      return( <MetroListView {... this.props} items={this.props.data} ref={this._captureRef} /> ); }else {
      return (
        <VirtualizedList
          {...this.props}
          renderItem={this._renderItem}
          getItem={this._getItem}
          getItemCount={this._getItemCount}
          keyExtractor={this._keyExtractor}
          ref={this._captureRef}
          onViewableItemsChanged={
            this.props.onViewableItemsChanged && this._onViewableItemsChanged
          }
        />
      );
    }
  }
Copy the code

MetroListView (internally ScrollView) is the old ListView implementation, while VirtualizedList is the new and better performing implementation. Let’s go to the file

Const itemCount = this.props. GetItemCount (data);if(itemCount > 0) { .... Omit some code}else if (ListEmptyComponent) {
      const element = React.isValidElement(ListEmptyComponent)
        ? ListEmptyComponent // $FlowFixMe
        : <ListEmptyComponent />;
      cells.push(
        /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
         * comment suppresses an error when upgrading Flow's support for React. * To see the error delete this comment and run Flow. */ 
      
        {element} 
      , ); }Copy the code

Here we see that the ListEmptyComponent we defined has a view with an inversionStyle attached to it.

const inversionStyle = this.props.inverted ? this.props.horizontal ? styles.horizontallyInverted : styles.verticallyInverted : null; Inverted: {transform: [{scaleX: -1}],}, horizontallyInverted: {transform: [{scaleX: -1}],},Copy the code

The above style adds an animation and does not set the height, so using height:’100%’ or flex:1 in the ListEmptyComponent has no effect and does not raise the height.

To achieve the desired effect, we need to set height to a specific value. So how big is this value set to? If you give the FlatList a style and the background property a color, you will find that the FlatList has a default height that takes up the rest of the screen (Flex: 1). We can set the height of the View in the ListEmptyComponent to the height of the FlatList. To get the height of the FlatList, we can use onLayout. Code adjustments:

// create variable fHeight = 0; <FlatList data={this.state.dataList} extraData={this.state} refreshing={this.state.isRefreshing} onRefresh={() => this._onRefresh()} keyExtractor={(item, index) => item.id} ItemSeparatorComponent={() => <View style={{ height: 1, backgroundColor:'#D6D6D6'}}/>} renderItem={this._renderItem} onLayout={e => this.fHeight = e.nativeEvent.layout.height} ListEmptyComponent={this.emptyComponent}/> // Define empty layout emptyComponent = () => {return <View style={{
            height: this.fHeight,
            alignItems: 'center',
            justifyContent: 'center',}} > < Text style = {{fontSize: 16}} > temporarily no data < / Text > < View >}Copy the code

Through the above adjustment, we found that when running on Android, the effect we want is achieved, but on iOS, it is not controllable, and occasionally the display is centered, and occasionally the display is on the top. The reason is that the timing of onLayout calls is slightly different on iOS than on Android (iOS renders the emptyComponent before the onLayout callback, and fHeight does not have a value).

So to apply the changed value to the emptyComponent, we set fHeight to state

state={
    fHeight:0
}

onLayout={e => this.setState({fHeight: e.nativeEvent.layout.height})}
Copy the code

This setting should be perfect, but…. It can still achieve the effect we want perfectly on Android, but there is a problem of back-and-forth screen on iOS. Print the log and find that the value is always 0 and the measured value is converted back and forth. Here we only need to measure the value, so we modify onLayout

                      onLayout={e => {
                          let height = e.nativeEvent.layout.height;
                          if (this.state.fHeight < height) {
                              this.setState({fHeight: height})
                          }
                      }}
Copy the code

After processing, on ios finally perfect to achieve the effect we want.

We should also use onEndReachedThreshold if we implement dropdown loading. In the FlatList, onEndReachedThreshold is a number type. It’s a number that triggers onEndReached when it says how far down the bottom is, It should be noted that the meanings of onEndReachedThreshold in FlatList and ListView are different. In ListView, onEndReachedThreshold is used when it indicates how many pixels are left at the bottom. The default value is 1000. FlatList, on the other hand, represents a multiple (also known as a ratio, not pixels), with a default value of 2. So as a general rule let’s look at the implementation

            <FlatList
                data={this.state.dataList}
                extraData={this.state}
                refreshing={this.state.isRefreshing}
                onRefresh={() => this._onRefresh()}
                ItemSeparatorComponent={() => <View style={{
                    height: 1,
                    backgroundColor: '#D6D6D6'}}/>} renderItem={this._renderItem} ListEmptyComponent={this.emptyComponent} onEndReached={() => this._onEndReached()} OnEndReachedThreshold = {} 0.1 / >Copy the code

Then we add the following code to componentDidMount

    componentDidMount() {
        this._onRefresh()
    }
Copy the code

Get more data from onEndReached and update data source dataList. It looks perfect, but….. _onRefresh takes time to load data. Render is executed before the data request is received, and there is no data available. The onEndReached method is executed once, and the data is loaded twice.

As for how many times onEndReachedThreshold should be used, we should be careful to set onEndReachedThreshold. If you interpret it as setting pixels, set it to a large number, such as 100, That’s fucked…. Personally, 0.1 is a good value.

Based on the above analysis, I feel it is necessary to carry out a secondary encapsulation of FlatList. I carried out a secondary encapsulation according to my own needs

import React, {
    Component,
} from 'react'
import {
    FlatList,
    View,
    StyleSheet,
    ActivityIndicator,
    Text
} from 'react-native'
import PropTypes from 'prop-types';

export const FlatListState = {
    IDLE: 0,
    LoadMore: 1,
    Refreshing: 2
};
export default class Com extends Component {
    static propTypes = {
        refreshing: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),
    };
    state = {
        listHeight: 0,
    }

    render() {
        var {ListEmptyComponent,ItemSeparatorComponent} = this.props;
        var refreshing = false;
        var emptyContent = null;
        var separatorComponent = null
        if (ListEmptyComponent) {
            emptyContent = React.isValidElement(ListEmptyComponent) ? ListEmptyComponent : <ListEmptyComponent/>
        } else{emptyContent = <Text style={styles.emptyText}> No data drop down to refresh </Text>; }if (ItemSeparatorComponent) {
            separatorComponent = React.isValidElement(ItemSeparatorComponent) ? ItemSeparatorComponent :
                <ItemSeparatorComponent/>
        } else {
            separatorComponent = <View style={{height: 1, backgroundColor: '#D6D6D6'}} / >; }if (typeof this.props.refreshing === "number") {
            if (this.props.refreshing === FlatListState.Refreshing) {
                refreshing = true}}else if (typeof this.props.refreshing === "boolean") {
            refreshing = this.props.refreshing
        } else if(typeof this.props.refreshing ! = ="undefined") {
            refreshing = false
        }
        return<FlatList {... this.props} onLayout={(e) => {let height = e.nativeEvent.layout.height;
                if(this.state.listHeight < height) { this.setState({listHeight: height}) } } } ListFooterComponent={this.renderFooter} onRefresh={this.onRefresh} onEndReached={this.onEndReached} Refreshing = {refreshing} onEndReachedThreshold = {this. Props. OnEndReachedThreshold | | 0.1} ItemSeparatorComponent={()=>separatorComponent} keyExtractor={(item, index) => index} ListEmptyComponent={() => <View style={{ height: this.state.listHeight, width:'100%',
                    alignItems: 'center',
                    justifyContent: 'center'
                }}>{emptyContent}</View>}
        />
    }

    onRefresh = () => {
        console.log("FlatList:onRefresh");
        if ((typeof  this.props.refreshing === "boolean" && !this.props.refreshing) ||
            typeof  this.props.refreshing === "number"&& this.props.refreshing ! == FlatListState.LoadMore && this.props.refreshing ! == FlatListState.Refreshing ) { this.props.onRefresh && this.props.onRefresh() } }; onEndReached = () => { console.log("FlatList:onEndReached");
        if (typeof  this.props.refreshing === "boolean" || this.props.data.length == 0) {
            return
        }
        if(! this.props.pageSize) { console.warn("pageSize must be set");
            return
        }
        if(this.props.data.length % this.props.pageSize ! = = 0) {return
        }
        if (this.props.refreshing === FlatListState.IDLE) {
            this.props.onEndReached && this.props.onEndReached()
        }
    };


    renderFooter = () => {
        let footer = null;
        if(typeof this.props.refreshing ! = ="boolean" && this.props.refreshing === FlatListState.LoadMore) {
            footer = (
                <View style={styles.footerStyle}>
                    <ActivityIndicator size="small" color="# 888888"/> <Text style={styles.footerText}> </Text> </View> ) }return footer;
    }
}
const styles = StyleSheet.create({
    footerStyle: {
        flex: 1,
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'center',
        padding: 10,
        height: 44,
    },
    footerText: {
        fontSize: 14,
        color: '# 555555',
        marginLeft: 7
    },
    emptyText: {
        fontSize: 17,
        color: '# 666666'}})Copy the code

In propTypes we use oneOfType to qualify refreshing. If the ListEmptyComponent is defined, we use a custom View, as can the ItemSeparatorComponent.

Adding a ListFooterComponent notifies the user that it is loading data. Refreshing does not have a drop-down list if it is a Boolean. If it is a number, the pageSize must be uploaded, If data source length and pageSize mod = 0, check whether more data is available (more data is available only when the last request is equal to pageSize, don’t call onEndReached if less than pageSize). Of course, the above code is very simple, I believe it is easy to understand, the other is not introduced. Welcome to point out any questions. The source address