每一個用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
歡迎各位看官的批評和指正,共同學習和成長
如果該文章對您有幫助,可以點個贊對錶示支持