React Native中useEffect本地未随状态变化触发,Snack中正常
问题:本地运行时红球需点击3次才移动,Expo Snack表现正常
问题场景
在Expo环境中点击按钮,红球每次都会按预期移动;但本地运行同一代码(仅ChoiceButton导入路径不同)时,红球每点击3次才会移动一次,屏幕录制显示useEffect调用时机异常。
核心原因
当前代码通过RangeMode状态中转触发位置更新,但点击同一按钮时,RangeMode被重复设置为相同值(比如多次点击"Soldat i värn"按钮时,RangeMode一直设为0)。React会跳过相同状态值的更新,导致依赖RangeMode的useEffect无法执行,进而不触发红球位置计算。
同时,RangeMode属于冗余中转状态,直接依赖按钮状态和选中枪支状态即可触发更新。
修复方案
- 移除冗余的
RangeMode状态 - 让位置更新逻辑直接依赖
buttonHiddenSoldier和choosenGun状态 - 简化按钮点击函数,无需设置
RangeMode
修改后的关键代码
// 移除RangeMode相关状态及对应useEffect // const [RangeMode, setRangeMode] = useState(0); // 替换为直接依赖按钮和枪支状态的useEffect useEffect(() => { console.log("更新红球位置"); update_shooting_range(); }, [buttonHiddenSoldier, choosenGun]); // 简化按钮点击逻辑 const updateButtonHiddenSoldier = () => { setbuttonHiddenSoldier(true); setbuttonSoldierInOpen(false); // 移除setRangeMode(0); }; const updateButtonSoliderInOpen = () => { setbuttonHiddenSoldier(false); setbuttonSoldierInOpen(true); // 移除setRangeMode(1); };
完整优化后代码
import React, { useEffect, useState, useRef } from "react"; import { View, Text, SafeAreaView, ImageBackground } from "react-native"; import ChoiceButton from "../../components/ChoiceButton"; const ShootingDistance = ({ navigation }) => { const [targetLayout, setTargetLayout] = useState(undefined); const [parentContainer, setParentContainer] = useState(undefined); const [ballPlacement, setBallPlacement] = useState(450); const [choosenGun, setchoosenGun] = useState("Kpist"); const [buttonHiddenSoldier, setbuttonHiddenSoldier] = useState(true); const [buttonSoldierInOpen, setbuttonSoldierInOpen] = useState(false); const [gunImage, setGunImage] = useState(null); // 直接依赖按钮状态和选中枪支触发位置更新 useEffect(() => { console.log("更新红球位置"); update_shooting_range(); }, [buttonHiddenSoldier, choosenGun]); const setValueGun = (value) => { if (value == 0) { setchoosenGun("Ak4"); } else if (value == 1) { setchoosenGun("Ak4 Kikarsikte"); } else if (value == 2) { setchoosenGun("Kpist"); } else if (value == 3) { setchoosenGun("Kulspruta 58"); } }; const update_shooting_range = () => { if (choosenGun == "Ak4") { setBallPlacement(buttonHiddenSoldier ? 300 : 500); } else if (choosenGun == "Ak4 Kikarsikte") { setBallPlacement(buttonHiddenSoldier ? 400 : 600); } else if (choosenGun == "Kpist") { setBallPlacement(buttonHiddenSoldier ? 150 : 250); } else if (choosenGun == "Kulspruta 58") { setBallPlacement(buttonHiddenSoldier ? 400 : 600); } }; const updateButtonHiddenSoldier = () => { setbuttonHiddenSoldier(true); setbuttonSoldierInOpen(false); }; const updateButtonSoliderInOpen = () => { setbuttonHiddenSoldier(false); setbuttonSoldierInOpen(true); }; const activateButtonHiddenSoldier = () => { updateButtonHiddenSoldier(); }; const activateButtonSoliderInOpen = () => { updateButtonSoliderInOpen(); }; // 合并视图引用为对象,简化代码结构 const viewRefs = { 600: useRef(null), 550: useRef(null), 500: useRef(null), 450: useRef(null), 400: useRef(null), 350: useRef(null), 300: useRef(null), 250: useRef(null), 200: useRef(null), 150: useRef(null), 100: useRef(null), 50: useRef(null), }; useEffect(() => { const ref = viewRefs[ballPlacement]?.current; if (ref) { ref.measure((x, y, width, height) => { setTargetLayout({ x, y, width, height }); }); } }, [ballPlacement]); return ( <ImageBackground source={require("../images/backgrounds/background.png")} resizeMode="cover" style={{ flex: 1, width: "100%", height: "100%" }} > <SafeAreaView style={{ flex: 1, margin: 10 }}> {targetLayout && parentContainer && ( <View style={{ backgroundColor: "red", width: targetLayout.height / 2, height: targetLayout.height / 2, borderRadius: targetLayout.height / 2, position: "absolute", zIndex: 2, left: targetLayout.x + parentContainer.x + targetLayout.width / 2 - targetLayout.height / 2, top: targetLayout.y + parentContainer.y + targetLayout.height / 4, }} /> )} <View style={{ flex: 1 }}> <Text style={{ flex: 1, color: "white", fontSize: 30, textAlign: "center", marginTop: 20, }} > Skjutavstånd </Text> <Text style={{ flex: 1, color: "white", fontSize: 20, textAlign: "center", }} > Välj ett vapen samt typ av mål för att se max rekommenderat skjutavstånd. </Text> </View> <View style={{ flex: 2, backgroundColor: "lightgray", width: "90%", marginHorizontal: 20, marginTop: 20, }} onLayout={(event) => { const layout = event.nativeEvent.layout; setParentContainer(layout); }} > <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", backgroundColor: "lightgray", borderBottomWidth: 1, }} ref={viewRefs[600]} > <Text style={{ textAlign: "right" }}>600 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 3, }} ref={viewRefs[550]} > <Text style={{ textAlign: "right" }}>550 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 1, }} ref={viewRefs[500]} > <Text style={{ textAlign: "right" }}>500 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 3, }} ref={viewRefs[450]} > <Text style={{ textAlign: "right" }}>450 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 1, }} ref={viewRefs[400]} > <Text style={{ textAlign: "right" }}>400 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 3, }} ref={viewRefs[350]} > <Text style={{ textAlign: "right" }}>350 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 1, }} ref={viewRefs[300]} > <Text style={{ textAlign: "right" }}>300 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 3, }} ref={viewRefs[250]} > <Text style={{ textAlign: "right" }}>250 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 1, }} ref={viewRefs[200]} > <Text style={{ textAlign: "right" }}>200 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 3, }} ref={viewRefs[150]} > <Text style={{ textAlign: "right" }}>150 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 1, }} ref={viewRefs[100]} > <Text style={{ textAlign: "right" }}>100 meter</Text> </View> <View style={{ flex: 1, borderColor: "black", borderStyle: "solid", borderBottomWidth: 3, flexDirection: "row", justifyContent: "space-between", }} ref={viewRefs[50]} > <Text style={{ flex: 1, textAlign: "right" }}>50 meter</Text> </View> </View> <View style={{ flex: 1, justifyContent: "center", alignItems: "center", }} > <Text style={{ flex: 1, color: "white", fontSize: 22 }}> Välj vapen (4) </Text> <Text style={{ flex: 1, color: "white", fontSize: 14, color: "yellow", fontWeight: "bold", }} > {choosenGun} </Text> </View> <View style={{ flex: 1, justifyContent: "center", alignItems: "center", }} > <Text style={{ flex: 1, color: "white", fontSize: 28 }}> Typ av mål </Text> <View style={{ flex: 1, flexDirection: "row", justifyContent: "space-between", marginBottom: 20, }} > <ChoiceButton ClickFunction={activateButtonHiddenSoldier} ButtonState={buttonHiddenSoldier} ButtonText="Soldat i värn" MarginRight={10} /> <ChoiceButton ClickFunction={activateButtonSoliderInOpen} ButtonState={buttonSoldierInOpen} ButtonText="Soldat öppet" /> </View> </View> </SafeAreaView> </ImageBackground> ); }; export default ShootingDistance;
额外优化说明
- 将分散的视图引用合并为对象,简化代码结构
- 用三元表达式简化
update_shooting_range中的条件判断 - 移除
measure中未使用的冗余参数pageX和pageY
内容的提问来源于stack exchange,提问作者Jhnsbrst
相关产品推荐
相关产品推荐

