[英]How to render 200+ view without performance issue in react-native
I am trying to make a game in react-native.我正在尝试在 react-native 中制作游戏。 I want to render 200+ views on the Game screen.
我想在游戏屏幕上呈现 200 多个视图。 Each View has a pressable functionality.
每个视图都有一个可按下的功能。 Whenever I press the View I need to run a function that will change the View background color and update score on the game context.
每当我按下视图时,我都需要运行 function 来更改视图背景颜色并更新游戏上下文中的分数。 But Whenever I try to press any View it took some time to change the background and update the context.
但是每当我尝试按下任何视图时,都需要一些时间来更改背景和更新上下文。
Note笔记
I am using the expo as a development environment and I am using a real device too.我正在使用 expo 作为开发环境,并且我也在使用真实设备。
My View Component我的视图组件
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);
Game Screen Component游戏画面组件
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",
},
});
How can I run view function immediately whenever I press it?每当我按下它时,如何立即运行视图 function?
Use pagination in flatlist在平面列表中使用分页
for ref: Pagination in flatlist参考:平面列表中的分页
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', }, });
The reason it is slow is that when you press on a view, all 200+ CheckBoxCom
components rerender.缓慢的原因是当您按下视图时,所有 200 多个
CheckBoxCom
组件都会重新呈现。 If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.如果他们不需要,我们可以通过尝试防止那些不必要的重新渲染来提高性能。
I believe the major bottleneck here is the gameContext
.我相信这里的主要瓶颈是
gameContext
。 It groups together a lot of states and if any of these were to change, all components will rerender.它将许多状态组合在一起,如果其中任何一个状态发生变化,所有组件都将重新呈现。 It provides
score
state that you are reading within each CheckBoxCom
.它提供您在每个
CheckBoxCom
中阅读的score
state 。 Whenever the score changes all CheckBoxCom
components will re-render.每当分数发生变化时,所有
CheckBoxCom
组件都会重新渲染。 If you change handlePress()
to:如果您将
handlePress()
更改为:
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score => score + 1);
};
Please note the use of callback to update the score in the above handler.请注意使用回调来更新上述处理程序中的分数。 In this case, we don't need to read
score
from context, so we can remove it from the game context provider, only pass setScore
.在这种情况下,我们不需要从上下文中读取
score
,因此我们可以将其从游戏上下文提供程序中移除,只需通过setScore
。 Removing score
from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score
.从上下文提供程序中删除
score
很重要,因为不这样做会使用上下文重新渲染所有组件,即使您没有专门解构score
。
Also, make sure you don't have a lot of state variables within a single context.此外,请确保在单个上下文中没有很多 state 变量。 Split it into multiple contexts if you have different states in there.
如果您在那里有不同的状态,请将其拆分为多个上下文。 In this way, you will be able to reduce unnecessary rerenders of the
CheckBoxCom
components.这样,您将能够减少
CheckBoxCom
组件的不必要重新呈现。
Since your CheckBoxCom
components have an internal state, using React.memo()
will not help to prevent rerenders because it only works for rerenders resulting from changed props.由于您的
CheckBoxCom
组件具有内部 state,因此使用React.memo()
将无助于防止重新渲染,因为它仅适用于由更改的道具导致的重新渲染。
But if you are able to refactor them to lift the active
state up to the parent ie something like activeViews
or something (which could be a map of indexes which are true
ie active), then you can pass the active
state as a boolean prop to each CheckBoxCom
component. But if you are able to refactor them to lift the
active
state up to the parent ie something like activeViews
or something (which could be a map of indexes which are true
ie active), then you can pass the active
state as a boolean prop to每个CheckBoxCom
组件。 And if we also pass setScore
via a prop instead of via context, we can benefit from React.memo()
.如果我们也通过 prop 而不是 context 传递
setScore
,我们可以从React.memo()
中受益。 BTW it is not necessary to wrap setState
methods with useCallback()
.顺便说一句,没有必要用
useCallback()
包装setState
方法。
The end result will be: CheckBoxCom
components with zero internal states and no reliance on context, in other words, pure components ie components which work nicely with React.memo()
.最终结果将是:内部状态为零且不依赖上下文的
CheckBoxCom
组件,换句话说,纯组件,即与React.memo()
配合得很好的组件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.