灵异组件

众所周知,列表是移动开发中非常常用的组件(控件)。原生的列表,拿iOS来来说,有UITableView & UICollectionview,就算是长列表的情况也只是加载速度慢,不会出现突然不再加载的情况,Android的应该也是这种情况。但是React-Native中提供的列表就会出现突然不再加载的情况,严格来说是列表的item不再渲染了,因为查看数据,发现列表的数据的数据是正确的。React-Native提供给开发者的列表组件有三种FlatList, SectionList,ListView,目前的使用情况来看,FlatList& SectionList都会出现这种灵异事件。

使用场景

国家手机号代码列表

由于项目的需要,目前的App需要提供一个全球的手机号代码列表,供用户注册的时候,选择自己国家的手机号代码。列表见下图:
图片描述

这个列表的数据一共是242条,由于需要排序分组并且给用户提供索引,所以采用sectionList来实现该页面。页面刚完成的时候,效果还算可以,除了快速滑动的时候会出现白屏,索引的跳转不是很准确(我从网上查了一下,那两个是React-native本省的问题),算是完成任务,就提交给策划,然后进行新功能的开发。

实现代码如下:

render(){

        return (<View style={styles.containStyle}>
                    <CustomNavBar title="Country" navigator={this.props.navigator}></CustomNavBar>
                    <SectionList ref={el=> this.sectionList=el}
                                 initialNumToRender={20}
                                 showsVerticalScrollIndicator={true}
                                //  onEndReachedThreshold={0.1}
                                //  onEndReached={()=> this.loadMoreData(false)}
                                 sections={this.state.totalSections}
                                 renderItem={this._renderItem}
                                 getItemLayout={this._getItemLayout}
                                 keyExtractor={(item: Item, index: number)=> index.toString()}
                                 extraData={this.state}
                                 renderSectionHeader={this._renderSectionHeader}
                                 ItemSeparatorComponent={()=><View style={{width: '100%', height: 1/PixelRatio.get(), backgroundColor: THEME_COLOR.SEPATOR_COLOR, marginLeft: 12, marginRight: 24}}></View>}>
                    </SectionList>
                    <SideBar indexs={this.state.indexs} onLetterSelectedListener={this.onSideBarSelected.bind(this)}></SideBar>
                </View>);
    }

    _renderItem = (info: any) => {

        return (<CountryCodeItem item={info.item} itemSelected={this.itemSelected.bind(this, info.item)}></CountryCodeItem>);
    }

    _getItemLayout = (data:any, index:number) => {

        const itemHeight = 40;
        return {
            length: itemHeight,
            offset: (itemHeight + 0) * index,
            index,
        };
    }

    _renderSectionHeader = (info: any) => {

        let txt = info.section.key;
        return <Text key={txt} style={{ height:20, textAlignVertical: 'center', backgroundColor: THEME_COLOR.BOX_BOTTOM_COLOR, color: '#666666', fontSize: 12, paddingLeft: 16, paddingTop: 4}}>{txt}</Text>
    }

    itemSelected(item: Item){

        console.log('the current item is===='+JSON.stringify(item));
        const result = item.countryCode + ' ' + item.phoneCode;
        if(this.props.updatePhoneCode){

            this.props.updatePhoneCode(result, item.phoneCode);
            this.props.navigator.pop();
        }
    }

    onSideBarSelected(letter:string){

        let sections: sectionData[] = this.state.totalSections;
        if (sections) {
            for (let index = 0; index < sections.length; index++) {
              let item = sections[index];
              if (item.key === letter) {

                console.log('the current index i ==' + index);
                this.sectionList.scrollToLocation({animated: true, itemIndex: 0, sectionIndex: index, viewPosition: 0.0});
                break;
              }
            }
        }
    }

好友列表

好友列表,好友列表的效果和上面一样,也是需要分组和索引,由于好友列表开发的比较早,我是采用的FlatList实现的功能。

代码如下:

renderSuccessView(){
        return (
            <View style={styles.container}>
                <View style={styles.content}>
                <FlatList
                    ref={el=> this.list = el}
                    data={this.state.listData}
                    extraData={this.state}
                    keyExtractor={(item: any, index: number)=> index.toString()}
                    renderItem={this._renderItem}
                    getItemLayout={this._getItemLayout}
                    ListEmptyComponent={this.renderEmptyView}
                    ItemSeparatorComponent={this.itemSeparator}
                    initialNumToRender={20}
                    //onEndReachedThreshold={0.8}
                    //legacyImplementation={this.state.listData.length > 0}
                    showsVerticalScrollIndicator={true}
                />
                <View style={{position: 'absolute', height: '100%', width: 15, right: 0, backgroundColor: 'transparent', justifyContent: 'center', alignItems: 'center'}}><SideBar indexs={this.indexs} onLetterSelectedListener={this.onSideBarSelected.bind(this)}/></View>
                </View>
                <View style={styles.divider}></View>
            </View>
        );
    }
    
    
    _renderItem = (item:any) => {
        console.log('the current item-->' + JSON.stringify(item)+" key: "+ item.item.key);
        return (<FriendListItem item={item.item} spreadValue={this.state.scaleValue} onListItemClick={this.onListItemClick.bind(this)} 
                    acceptNewFriend={this.acceptNewFriend.bind(this)}
                    newFriendsSectionUnfold={this.newFriendsSectionUnfold.bind(this)}>
                </FriendListItem>);
    }

好友列表刚开始做的时候bug比较多,经过几次修改,修复了出现的那些bug,效果也打到达了策划的要求。本以为任务完成了,还算完美。但是在接下来的反复测试中,灵异事件出现了。

灵异事件

在测试的过程中,出现了好友列表只加载10条的情况,10是initialNumToRender设置第一屏渲染的数据条数。而且很奇怪,好友列表第一个固定的分组是好友的申请记录,其余的显示已经是好友的数据,按拼音的首字母进行分组排序。好友申请记录的显示逻辑是一开始最多显示5条,点击展开按钮,在全部显示出来。

好友列表的灵异事件刚开始发现的时候,好友申请记录显示了5条,已经是好友的数据显示了5条,当点击了申请记录的展开按钮,申请记录是6条,已经是好友的数据就少了最后一条,变成了4条,但是把申请收齐,好友数据又成了5条了。

刚开始以为是之前的bug没有改彻底,之前因为因为有key重复的情况,出现了数据缺少的情况,经过反复review代码,发现不是那个bug。反复进入好友列表,发现只渲染10条的灵异事件,低端机上比较容易出现,比如iphone5S,Android的低端机上也比较容易出现,高端机上出现的频率很低。

于是去测试国家编码列表,那个列表一共是242条数据,也是右边有分组索引,也出现了同样的问题。

而且出现的时候列表的可滑动区域变得很大,打印数据源,发现数据源是正常的,只是屏幕上只渲染了initialNumToRender设置的条数,就算你设置了2,也只显示2条,设置一个很大的值,也能渲染,就是渲染慢,要等待,数据条数越多,时间越长。

网上查了很多资料,尝试了很多解决办法。

  • 1.分页加载数据。
  • 2.列表的Item继承PureComponent。
  • 3.使用ListView替代。

分页加载

分页加载是网上查找到的,说是FlatList& SectionList提供了分页加载的触发方法,当一屏数据快要滑动到底部的时候,触发一个方法,可以再去加载另外一部分数据。于是乎赶紧实现了一把,数据也是分页加载的,经过反复测试,发现分页加载的情况下,也会出现不再渲染的情况。而且因为列表右边有索引,分页加载其实也不能满足需求,本想着分页加载能够解决灵异事件,去和策划沟通一下,索引的功能稍微改一改,结果发现分页加载的思路也行不通。

item继承PureComponent

网上查到一份资料,有人说是把列表的item的直接继承PureComponent(之前继承的是React.Component),于是满怀希望赶紧把item重新改写了一下,最后发现然并卵,那个灵异效果依旧会出现。

使用ListView替代

使用ListView替代,查了一下关芳芳文档,说是ListView本身的性能不好,才重新封装了
FlatList& SectionList来替代它。用ListView替代以后,列表能够完全渲染了,就是有点慢,而且官方文档说ListView在超出屏幕之外,并没有做回收,数据量打了,终究是个隐患。内存暴涨,滑动卡顿。

其实在测试的过程中,发现如果直接把 列表的initialNumToRender设置成列表数据的长度,也能暂时解决问题,但是数据量大了,加载会很慢。拿国家编码列表来举例,242条数据,大概需要卡住5s左右,随着数据量的增大,效果可想而知,好友列表的数据容纳量目标是1500条,瞬间就泪崩了。

总结

目前来说,经过网上查询资料,自己尝试,都木有彻底解决React-Native列表的灵异问题,目前的解决方案,只是暂时性,数据量大了,问题会很明显,希望遇到过同样问题的同学,能够一起讨论解决,共同成长,或者有解决方案,麻烦告知一声,谢谢了。


蓝光95
210 声望16 粉丝

一名从业多年的软件开发者,做过5年的iOS开发,做过一年的react-native开发,有iOS性能优化经验,IM开发经验,会小程序的开发,现在在昆明从事移动前端开发的工作。