React Native渲染200+可点击视图的性能优化方案咨询
React Native Expo游戏中大量点击视图的响应延迟优化
问题描述
在Expo环境下开发React Native游戏,需渲染200+带点击功能的视图。点击视图时需执行切换背景色、更新gameContext中分数的操作,但点击后响应存在延迟。以下是相关组件代码:
CheckBoxCom组件
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页面组件
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", }, });
优化方案及代码修改
1. 修复setScore的闭包陷阱
原代码中setScore(score + 1)依赖闭包捕获的旧score值,可能导致分数更新不及时,改为函数式更新确保获取最新状态:
// CheckBoxCom组件中handlePress修改为 const handlePress = () => { if (active) return; setActive(true); setScore(prevScore => prevScore + 1); };
2. 简化组件层级,减少不必要嵌套
移除CheckBoxCom中外层多余的View,直接用Pressable包裹内部视图,减少组件层级提升渲染效率:
// CheckBoxCom组件的return部分修改为 return ( <Pressable onPress={handlePress} style={{ margin: 3 }}> <View style={{ width: 20, height: 20, borderWidth: 2, borderColor: active ? "green" : "gray", borderRadius: 3, backgroundColor: active ? "green" : null, }} /> </Pressable> );
3. 优化定时器逻辑,减少不必要的定时器创建
调整useEffect的触发条件,仅当active变为true时才启动定时器,避免无效操作:
// CheckBoxCom组件中的useEffect修改为 useEffect(() => { if (!active) return; const timer = setTimeout(() => { setActive(false); }, Math.floor(Math.random() * 35000)); return () => clearTimeout(timer); }, [active]);
4. 将游戏结束逻辑移至useEffect,避免渲染阶段副作用
原Game组件中在渲染阶段直接处理time === 0的逻辑,会导致重复执行和性能损耗,改为监听time变化的useEffect:
// Game组件中添加useEffect监听time变化 useEffect(() => { if (time === 0) { clearInterval(intervalId); navigation.navigate("Score", { score }); } }, [time, intervalId, navigation, score]); // 移除原渲染阶段的if判断 // if (time === 0) { ... }
5. 优化FlatList配置,提升批量渲染性能
调整FlatList的渲染参数,让组件分批渲染避免主线程阻塞:
// Game组件中的FlatList属性修改为 <FlatList style={{ alignSelf: "center" }} data={boxList} initialNumToRender={100} // 初始渲染数量,根据屏幕适配调整 maxToRenderPerBatch={20} // 每批渲染的组件数 windowSize={5} // 控制预渲染的范围,减少内存占用 removeClippedSubviews={true} // 自动裁剪离屏组件 numColumns={12} renderItem={() => <CheckBox />} keyExtractor={(item) => item.toString()} scrollEnabled={false} />
6. 清理冗余样式
移除Game组件样式中无效的display: "flex"(React Native中默认flex布局):
const styles = StyleSheet.create({ textHeader: { flexDirection: "row", justifyContent: "space-between", width: "100%", marginTop: 40, paddingHorizontal: 30, }, checkBoxContainer: { margin: 20, height: "80%", overflow: "hidden", }, });
修改后的完整代码
CheckBoxCom组件
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 { setScore } = useContext(gameContext); useEffect(() => { if (!active) return; const timer = setTimeout(() => { setActive(false); }, Math.floor(Math.random() * 35000)); return () => clearTimeout(timer); }, [active]); const handlePress = () => { if (active) return; setActive(true); setScore(prevScore => prevScore + 1); }; return ( <Pressable onPress={handlePress} style={{ margin: 3 }}> <View style={{ width: 20, height: 20, borderWidth: 2, borderColor: active ? "green" : "gray", borderRadius: 3, backgroundColor: active ? "green" : null, }} /> </Pressable> ); } export default memo(CheckBoxCom);
Game页面组件
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(null); useEffect(() => { const int = setInterval(() => { setTime(prvTime => prvTime - 1); }, 1000); setIntervalId(int); return () => clearInterval(int); }, []); useEffect(() => { if (time === 0) { clearInterval(intervalId); navigation.navigate("Score", { score }); } }, [time, intervalId, navigation, 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={100} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={true} numColumns={12} renderItem={() => <CheckBox />} keyExtractor={(item) => item.toString()} scrollEnabled={false} /> </View> </View> ); } const styles = StyleSheet.create({ textHeader: { flexDirection: "row", justifyContent: "space-between", width: "100%", marginTop: 40, paddingHorizontal: 30, }, checkBoxContainer: { margin: 20, height: "80%", overflow: "hidden", }, });
内容的提问来源于stack exchange,提问作者Khorshed Alam
相关产品推荐
相关产品推荐

