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

歡迎各位看官的批評和指正,共同學習和成長

如果該文章對您有幫助,可以點個贊對錶示支持

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章