FlatList 优化

每一个用react-native来开发项目的人,免不了会采一些坑,今天我们拿 列表来说事儿,正如标题所说,既然是优化,那么我们得从坑说起。

先看一段代码(最有说服力,和最直观的效果就是运行代码看结果):

import React, {Component} from "react";
import {
  StyleSheet, Text, View, FlatList, Dimensions,
} from "react-native";

const {width, height} = Dimensions.get('window');//屏幕宽度

const DATA_LIST = [{key: 'a'}, {key: 'b'},]

export default class FlatListPage extends Component {

  constructor(props) {
    super(props);
    this.state = {
      data: []
    };
  }

  componentDidMount() {
    this.setState({data: this.state.data.concat(DATA_LIST)})
  }

  _keyExtractor = (item, index) => index + "";

  _pullUpLoading = () => {
    this.setState({data: this.state.data.concat(DATA_LIST)})
  }

  _onEndReached = () => {
    console.log(1111111, '--------_onEndReached---------')
    this._pullUpLoading();
  }

  _onLayout = (env) => {
    // let {layout} = env.nativeEvent
    // console.log(11111222222, layout.width, layout.height)
  }


  render() {
    return (
      <View style={[styles.container, {marginTop: 44}]}>
        <FlatList
          style={styles.flatListStyle}
          data={this.state.data}
          keyExtractor={this._keyExtractor}
          // contentContainerStyle={{backgroundColor: 'blue', flex: 1}}
          // contentContainerStyle={{backgroundColor: 'blue', height: 200}} height > 2 * 40
          onEndReachedThreshold={0.01} // 决定当距离内容最底部还有多远时触发onEndReached回调
          scrollEventThrottle={16}
          showsVerticalScrollIndicator={false}
          onEndReached={this._onEndReached}
          onLayout={this._onLayout} // 当组件挂载或者布局变化的时候调用,参数为:: {nativeEvent: { layout: {x, y, width, height}}} 这个事件会在布局计算完成后立即调用一次,不过收到此事件时新的布局可能还没有在屏幕上呈现,尤其是一个布局动画正在进行中的时候。
          renderItem={({item}) =>
            <View style={{
              width: width,
              height: 40,
              marginTop: 10,
              justifyContent: 'center',
              alignItems: 'center',
              backgroundColor: 'gray'
            }}>
              <Text style={{fontSize: 20,}}>{item.key}</Text>
            </View>
          }
        />
      </View>
    );
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  flatListStyle: {
    width: width,
    flex: 1,
    backgroundColor: 'red',
  }
});

如上代码运行,你会发现如图效果  :  此时的 contentContainerStyle 没有属性

满满一屏幕的元素,控制台打印出 onEndReached 方法执行了多次,有时候你也会看到 如下图所示的效果

onEndReached 并未执行,页面也如你所愿只渲染了一次数据

 

当 contentContainerStyle = {flex: 1}  或者  contentContainerStyle = {height: 200 } 这里手动设置高度必须要大于 item 渲染个数的高度 再次渲染数据,结果如下图:(最多只会 执行一次 onEndReachEnd 事件,当然有时也会达到如你所愿的效果)

 

原因分析:

在分析原因之前先引入两个 属性  
onEndReachedThreshold   官方解释:决定当距离内容最底部还有多远时触发onEndReached回调,这里其实是针对 contentContainerStyle 属性的底部

onLayout  官方解释:当组件挂载或者布局变化的时候调用,参数为:: {nativeEvent: { layout: {x, y, width, height}}} 这个事件会在布局计算完成后立即调用一次,不过收到此事件时新的布局可能还没有在屏幕上呈现,尤其是一个布局动画正在进行中的时候。

当页面进行绘制的时候,新的布局还没有绘制完成的时候,onEndReachedThreshold = {0.01} 的属性一直是成立的,所以会触发 onEndReached, 导致页面的渲染不尽人意,当你没有 contentContainerStyle 属性时(实际开发中,我相信大多数应用场景都可以不用),也就导致了 页面一边绘制一边具有高度,结果就导致了 onEndReachedThreshold 始终满足条件,onEndReached 就不停的执行,直到页面被填满

 

实际项目中的优化代码如下(一下是实际生产项目的部分代码,只提供 FlatList 优化部分的注释和解答,关键在于 _isAllowToFetch 变量来控制 ):

/** react 组建的引用 */
import React, { Component } from "react";
import { StyleSheet, Text, View, FlatList, Image } from "react-native";

/** 全局样式的引用 */
import HBStyle from "../../styles/standard";
import CommonSize from "../../utility/size";

/** 第三方依赖库的引用 */

/** 自定义组建的引用 */
import withTemplate from "../../components/HOC/withTemplate";
import withOnScroll from "../../components/HOC/withOnScrollToNav";
import CTouchableWithoutFeedback from "../../components/CTouchableWithoutFeedback";

/** 模板页面的引用 */
import ResultPageNoTitle from "../../components/ResultPageNoTitle";

/** 工具类的引用 */
import WebAPI from "../../utility/webAPI";
import Util from "../../utility/util";
import BorrowerToken from "../mobx";
import NativeModule from "../../utility/nativeModule";
import { observer } from "mobx-react";

/** 常量声明 */
const WithScrollView = withOnScroll(FlatList); // 高阶组件返回的 FlatList
const NOData_Icon = require("../../res_img/common/common_img_blank.png");

@observer
@withTemplate
export default class Notice extends Component {

  _isAllowToFetch = false; // 是否允许上拉加载控制变量

  constructor(props) {
    super(props);
    this.state = {
      hasMoreNoticeData: true, // 是否收到过通知
      page: 1, // 页数
      rows: 10, // 每页条数
      data: [],
      isShowFooterCom: false // 默认不展示
    };
    this.navConfig = {
      title: "通知"
    };
  }

  componentDidMount() {
    this._getNotice(true);
  }

  componentWillMount() {}

  componentWillUnmount() {}

  componentWillReceiveProps(nextProps, nextState) {}

  shouldComponentUpdate(nextProps) {
    return true;
  }

  /** 获取通知列表 */
  _getNotice = isInitFetch => {
    const { page, rows } = this.state;
    WebAPI.Borrower.GetClaimsTransferNoticeList(BorrowerToken.token, page, rows).success(res => {
      if (res.success) {
        if (Util.isArray(res.data) && res.data.length > 0) {
          this.setState({ data: this.state.data.concat(res.data), hasMoreNoticeData: true });
          if (res.data.length >= 10) {
            this._isAllowToFetch = true; // 当后台接口返回的数据不止一页的时候,允许上拉加载,可以执行该事件
          } else {
            this._isAllowToFetch = false; // 否则就不允许上拉加载
            this.setState({ isShowFooterCom: true });
          }
          this.state.page += 1;
        } else {
          this._isAllowToFetch = false; // 接口请求失败更是不可能上拉加载
          isInitFetch && this.setState({ hasMoreNoticeData: false });
        }
      } else if (!res.success && res.errorCode === "100001") {
        Util.alert.show({
          content: "你的荷包已在其他设备上登录,如非本人操作,则密码可能已泄露,请重新登录后立即修改登录密码。",
          buttons: [{ title: "我知道了" }]
        });
      }
    });
  };
  /** 跳转到转让协议 */
  _goTransfer = item => {
    NativeModule.RNBridge.jsBridgeOpen(item.noticeUrl);
  };

  _renderItem = ({ item }) => {
    return (
      <CTouchableWithoutFeedback handle={() => this._goTransfer(item)}>
        <View style={styles.itemsWrapper}>
          <Text
            style={{
              fontSize: 16,
              color: HBStyle.color.wblack,
              marginBottom: 19.5
            }}
          >
            {"债权转让通知书"}
          </Text>
          <View style={{ ...HBStyle.layout.rsbc }}>
            <View style={{ ...HBStyle.layout.rsbc }}>
              <Image style={{ width: 24, height: 24, marginRight: 7 }} source={require("../../res_img/borrower/image/news_img_notice.png")} />
              <Text style={{ fontSize: 13, color: HBStyle.color.wgray_main }}>{item.noticeName}</Text>
            </View>
            <View style={{ ...HBStyle.layout.rsbc }}>
              <Text style={{ fontSize: 13, color: HBStyle.color.wgray_main }}>{Util.formatDate(item.transferDate, "yyyy-m-d HH:MM")}</Text>
            </View>
          </View>
        </View>
      </CTouchableWithoutFeedback>
    );
  };

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

  /** 下拉刷新 */
  _onEndReached = () => {
    if (!this._isAllowToFetch) return;  // 初始化页面,不会进行数据的加载
    this._getNotice();
  };

  _footerCom = () => {
    return (
      <View style={[HBStyle.layout.ccc, { height: 40 }]}>
        <Text style={{ fontSize: 13, color: HBStyle.color.wgray_main }}>{"没有更多啦~"}</Text>
      </View>
    );
  };

  render() {
    const { isShowFooterCom } = this.state;
    return (
      <View style={styles.container}>
        {!this.state.hasMoreNoticeData ? (
          <ResultPageNoTitle imgurl={NOData_Icon} textView={() => <Text style={styles.describeFont}>{"还没收到过通知"}</Text>} />
        ) : (
          <WithScrollView
            ref={ref => (this._pullInstance = ref)}
            contentContainerStyle={{ paddingHorizontal: 12 }}
            style={styles.flatListStyle}
            keyExtractor={this._keyExtractor}
            data={this.state.data}
            renderItem={this._renderItem}
            onEndReached={this._onEndReached}
            ListFooterComponent={isShowFooterCom ? this._footerCom : null}
            onEndReachedThreshold={0.01}
            scrollEventThrottle={16}
            showsVerticalScrollIndicator={false}
          />
        )}
      </View>
    );
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    width: CommonSize.screen.width,
    backgroundColor: HBStyle.color.gray_bg,
    paddingBottom: Util.isIPhoneX() ? 34 : 0
  },
  flatListStyle: {
    flex: 1
  },
  itemsWrapper: {
    paddingHorizontal: 12,
    backgroundColor: HBStyle.color.white_bg,
    borderRadius: 5,
    paddingTop: 15,
    paddingBottom: 13.5,
    marginTop: 12,
    height: 94,
    flex: 1
  },
  describeFont: {
    fontSize: HBStyle.font.Body2,
    color: "#bebebe",
    textAlign: "center"
  }
});

 

更多的干货请点击   https://blog.csdn.net/woleigequshawanyier

react-native 实战项目demo https://github.com/15826954460/BXStage

欢迎各位看官的批评和指正,共同学习和成长

希望该文章对您有帮助,也希望得到您的鼓励和支持

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值