如何在react native中呈现200多个视图而不出现性能问题



我正在尝试用react native制作一个游戏。我想在游戏屏幕上渲染200多个视图。每个视图都具有可按下的功能。每当我按下View时,我都需要运行一个函数来更改View背景色并更新游戏上下文中的分数。但每当我尝试按下任何View时,都需要一些时间来更改背景和更新上下文。

注意

我把世博会作为一个开发环境,我也在使用一个真正的设备。

我的视图组件

import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
const [active, setActive] = useState(false);
const { score, setScore } = useContext(gameContext);
useEffect(() => {
let time = setTimeout(() => {
setActive(false);
}, Math.floor(Math.random() * 35000));
return () => clearTimeout(time);
}, [active]);
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score + 1);
};
return (
<View>
<Pressable onPress={handlePress}>
<View
style={{
width: 20,
height: 20,
borderWidth: 2,
borderColor: active ? "green" : "gray",
margin: 3,
borderRadius: 3,
backgroundColor: active ? "green" : null,
}}
></View>
</Pressable>
</View>
);
}
export default memo(CheckBoxCom);

游戏屏幕组件

import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";
export default function Game({ navigation }) {
const { score, time, setTime, boxList } = useContext(gameContext);
const [intervalId, setIntervalId] = useState("");
useEffect(() => {
const int = setInterval(() => {
setTime((prvTime) => prvTime - 1);
}, 1000);
setIntervalId(int);
return () => clearInterval(int);
}, []);
if (time === 0) {
clearInterval(intervalId);
navigation.navigate("Score", { score });
}
return (
<View style={{ flex: 1 }}>
<StatusBar style="auto" />
<View style={styles.textHeader}>
<Text>Score : {score}</Text>
<Text>Time Left: {time}s</Text>
</View>
<View style={styles.checkBoxContainer}>
<FlatList
style={{ alignSelf: "center" }}
data={boxList}
initialNumToRender={50}
numColumns={12}
renderItem={(i) => <CheckBox />}
keyExtractor={(i) => i.toString()}
scrollEnabled={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
textHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
marginTop: 40,
paddingHorizontal: 30,
},
checkBoxContainer: {
margin: 20,
display: "flex",
flexWrap: "wrap",
height: "80%",
overflow: "hidden",
flexDirection: "row",
},
});

如何在每次按下视图功能时立即运行该功能?

它之所以慢,是因为当您按下视图时,所有200多个CheckBoxCom组件都会重新出现。如果他们不需要,我们可以通过尝试防止那些不必要的重发来提高性能。

我认为这里的主要瓶颈是gameContext。它将许多状态组合在一起,如果其中任何一个状态发生变化,所有组件都将重新提交。它提供您在每个CheckBoxCom中读取的score状态。每当分数发生变化时,所有CheckBoxCom组件都将重新渲染。如果将handlePress()更改为:

const handlePress = () => {
if (active) return;
setActive(true);
setScore(score => score + 1);
};

请注意在上面的处理程序中使用回调来更新分数。在这种情况下,我们不需要从上下文中读取score,所以我们可以从游戏上下文提供程序中删除它,只传递setScore。从上下文提供程序中删除score很重要,因为如果不这样做,即使您没有专门销毁score,也会使用上下文重新发送所有组件。

此外,请确保在一个上下文中没有太多的状态变量。如果你有不同的状态,就把它分成多个上下文。通过这种方式,您将能够减少CheckBoxCom组件不必要的重新转发器。

由于CheckBoxCom组件具有内部状态,因此使用React.memo()将无助于阻止重新转发器,因为它只适用于因道具更改而导致的重新转发器。

但是,如果您能够重构它们以将active状态提升到父级,即类似activeViews或其他东西(可以是true的索引映射,即活动(,那么您可以将active状态作为布尔道具传递给每个CheckBoxCom组件。如果我们也通过道具而不是上下文传递setScore,我们可以从React.memo()中受益。顺便说一下,不需要用CCD_ 22来包装CCD_ 21方法。

最终结果将是:CheckBoxCom组件具有零内部状态,不依赖上下文,换句话说,纯组件,即与React.memo()良好工作的组件。

在平面列表中使用分页

参考:在平面中分页

import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
Platform,
ActivityIndicator,
} from 'react-native';
export default class App extends Component {
constructor() {
super();
this.state = {
loading: true,
//Loading state used while loading the data for the first time
serverData: [],
//Data Source for the FlatList
fetching_from_server: false,
//Loading state used while loading more data
};
this.offset = 0;
//Index of the offset to load from web API
}

componentDidMount() {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset : "+this.offset);
console.log(responseJson.slice((this.offset*12),((this.offset+1)*12)-1));
//Successful response from the API Call 
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
// serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
//adding the new data with old one available in Data Source of the List
loading: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
}

loadMoreData = () => {
//On click of Load More button We will call the web API again
this.setState({ fetching_from_server: true }, () => { 
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset Load : "+this.offset);
console.log(responseJson);
//Successful response from the API Call 
this.offset = this.offset + 1;

//After the response increasing the offset for the next API call.
this.setState({
//serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
fetching_from_server: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
});
};
renderFooter() {
return (
//Footer View with Load More button
<View style={styles.footer}>
<TouchableOpacity
activeOpacity={0.9}
onPress={this.loadMoreData}
//On Click of button calling loadMoreData function to load more data
style={styles.loadMoreBtn}>
<Text style={styles.btnText}>Loading</Text>
{this.state.fetching_from_server ? (
<ActivityIndicator color="white" style={{ marginLeft: 8 }} />
) : null}
</TouchableOpacity>
</View>
);
}
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
style={{ width: '100%' }}
keyExtractor={(item, index) => index}
data={this.state.serverData}
renderItem={({ item, index }) => (
<View style={styles.item}>
<Text style={styles.text}>
{item.currency}
{'.'} 
{item.code}
</Text>
</View>
)}
onEndReached={this.loadMoreData}
onEndReachedThreshold ={0.1}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListFooterComponent={this.renderFooter.bind(this)}
//Adding Load More button as footer component
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 30,
},
item: {
padding: 10,height:80
},
separator: {
height: 0.5,
backgroundColor: 'rgba(0,0,0,0.4)',
},
text: {
fontSize: 15,
color: 'black',
},
footer: {
padding: 10,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
loadMoreBtn: {
padding: 10,
backgroundColor: '#800000',
borderRadius: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
btnText: {
color: 'white',
fontSize: 15,
textAlign: 'center',
},
});

最新更新