React Native-Expo安卓应用点击5次后无响应且无报错求助
Expo React Native 应用点击5次后冻结问题排查与修复
问题描述
开发首个基于Expo/React Native(后端搭配Express-Mongoose)的安卓应用时,点击表情按钮5次后应用完全冻结,无法继续操作且无任何报错提示。移除定时器代码后问题依旧。
问题代码
import { StatusBar } from 'expo-status-bar'; import { StyleSheet, Text, View, Pressable } from 'react-native'; import { useState, useEffect } from 'react'; export default function App() { const [click, setClick] = useState({ frage: "serviceallgemein", like: "", datum: "" }); const [savebar, setSave] = useState({ savebar: false }); const [thankyou, setthankyou] = useState({ thankyou: false }); const handleClick4 = () => { setClick((prev) => ({ ...click, like: 4, datum: new Date().toLocaleString(), })) setSave((prev) => ({ savebar: true })) } const handleClick3 = () => { setClick((prev) => ({ ...click, like: 3, datum: new Date().toLocaleString(), })) setSave((prev) => ({ savebar: true })) } const handleClick2 = () => { setClick((prev) => ({ ...click, like: 2, datum: new Date().toLocaleString(), })) setSave((prev) => ({ savebar: true })) } const handleClick1 = () => { setClick((prev) => ({ ...click, like: 1, datum: new Date().toLocaleString(), })) setSave((prev) => ({ savebar: true })) } const setAlarm = () => { setSave((prev) => ({ savebar: false })); setthankyou((prev) => ({ thankyou: true })); } useEffect(() => { if (savebar.savebar === true) { if (click.like != "") { const checkfetch = async () => await fetch('http://192.168.2.165:9000/like'); checkfetch().then(res => res.text()) .then(res => { if (res == "nodeisok") { const fetching = async () => await fetch('http://192.168.2.165:9000/like', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ frage: click.frage, like: click.like, datum: click.datum }), }); fetching().then( setAlarm()) .catch((error) => { alert("No connection"); }) } }).catch((error) => { alert("No connection"); }); } } const setstatethankyou = () => { setthankyou((prev) => ({ thankyou: false })) }; const timer = setTimeout(() => setstatethankyou(), 4000); return () => { clearTimeout(timer) } }, [click]) return (<View style={styles.container}> {thankyou.thankyou == true ? <View style={styles.containerthankyou}> <Text style={styles.thankyou}>Vielen Dank!</Text> </View> : <View style={styles.containernormal}> <Text style={styles.frage}>Wie gefällt Ihnen unsere Service?</Text> <View style={styles.buttonbox}> <Pressable style={styles.buttons} onPress={handleClick4}><Text style={styles.smiley} >😍</Text></Pressable> <Pressable style={styles.buttons} onPress={handleClick3}><Text style={styles.smiley} >🙂</Text></Pressable> <Pressable style={styles.buttons} onPress={handleClick2}><Text style={styles.smiley} >😐</Text></Pressable> <Pressable style={styles.buttons} onPress={handleClick1}><Text style={styles.smiley} >☹️</Text></Pressable></View> <StatusBar style="auto" /> </View>} </View>); } const styles = StyleSheet.create({ // 样式内容省略 });
问题根源与修复方案
1. 状态更新闭包错误
所有handleClickX函数里,setClick的回调中使用了...click而非...prev,这会导致每次更新都基于组件初始渲染时的click值(闭包陷阱),而非最新的状态。多次点击后状态会出现混乱,引发不可预知的重渲染和逻辑错误。
修复:
将...click替换为...prev:
const handleClick4 = () => { setClick((prev) => ({ ...prev, // 改用最新的prev状态 like: 4, datum: new Date().toLocaleString(), })) setSave(true) } // 其他handleClick函数同理修改
2. 冗余的对象类型状态
savebar和thankyou用对象存储单个布尔值完全没必要,不仅增加代码复杂度,还容易在更新时出现引用问题,导致组件不必要的重渲染。
修复:
改为直接使用布尔值状态:
// 替换原状态定义 const [savebar, setSavebar] = useState(false); const [thankyou, setThankyou] = useState(false); // 对应的更新逻辑修改 const handleClick4 = () => { setClick((prev) => ({ ...prev, like: 4, datum: new Date().toLocaleString(), })) setSavebar(true) } const setAlarm = () => { setSavebar(false); setThankyou(true); } // useEffect里的判断改为 if (savebar === true) { ... } // 渲染判断改为 {thankyou ? ... : ...}
3. useEffect依赖数组不完整
useEffect中用到了savebar,但依赖数组里只加了click,这会导致useEffect捕获的是旧的savebar值,逻辑判断失效,多次点击后会重复触发fetch请求,造成请求堆积阻塞UI。
修复:
将savebar加入依赖数组,同时添加加载状态防止重复点击:
// 新增加载状态防止重复点击 const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (savebar && !isLoading && click.like !== "") { setIsLoading(true); const checkAndPost = async () => { try { const checkRes = await fetch('http://192.168.2.165:9000/like'); const checkText = await checkRes.text(); if (checkText === "nodeisok") { await fetch('http://192.168.2.165:9000/like', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ frage: click.frage, like: click.like, datum: click.datum }), }); setAlarm(); } } catch (error) { alert("No connection"); } finally { setIsLoading(false); } }; checkAndPost(); } const timer = setTimeout(() => setThankyou(false), 4000); return () => clearTimeout(timer); }, [click, savebar]) // 加入savebar到依赖数组 // 渲染时禁用加载中的按钮 <Pressable style={styles.buttons} onPress={handleClick4} disabled={isLoading}> <Text style={styles.smiley} >😍</Text> </Pressable> // 其他按钮同理添加disabled={isLoading}
4. Fetch请求处理错误
原代码中fetching().then(setAlarm())是直接执行setAlarm,而非在请求完成后执行,应该传递函数引用;同时混用async/await和.then链式调用,代码可读性差且容易出错。
修复:
改用全async/await写法,确保setAlarm在请求成功后执行。
修复后完整代码示例
import { StatusBar } from 'expo-status-bar'; import { StyleSheet, Text, View, Pressable } from 'react-native'; import { useState, useEffect } from 'react'; export default function App() { const [click, setClick] = useState({ frage: "serviceallgemein", like: "", datum: "" }); const [savebar, setSavebar] = useState(false); const [thankyou, setThankyou] = useState(false); const [isLoading, setIsLoading] = useState(false); const handleClick = (likeValue) => { // 合并重复的handleClick函数,减少代码冗余 setClick((prev) => ({ ...prev, like: likeValue, datum: new Date().toLocaleString(), })) setSavebar(true); } const setAlarm = () => { setSavebar(false); setThankyou(true); } useEffect(() => { if (savebar && !isLoading && click.like !== "") { setIsLoading(true); const checkAndPost = async () => { try { const checkRes = await fetch('http://192.168.2.165:9000/like'); const checkText = await checkRes.text(); if (checkText === "nodeisok") { await fetch('http://192.168.2.165:9000/like', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ frage: click.frage, like: click.like, datum: click.datum }), }); setAlarm(); } } catch (error) { alert("No connection"); } finally { setIsLoading(false); } }; checkAndPost(); } const timer = setTimeout(() => setThankyou(false), 4000); return () => clearTimeout(timer); }, [click, savebar]) return ( <View style={styles.container}> {thankyou ? ( <View style={styles.containerthankyou}> <Text style={styles.thankyou}>Vielen Dank!</Text> </View> ) : ( <View style={styles.containernormal}> <Text style={styles.frage}>Wie gefällt Ihnen unsere Service?</Text> <View style={styles.buttonbox}> <Pressable style={styles.buttons} onPress={() => handleClick(4)} disabled={isLoading} > <Text style={styles.smiley} >😍</Text> </Pressable> <Pressable style={styles.buttons} onPress={() => handleClick(3)} disabled={isLoading} > <Text style={styles.smiley} >🙂</Text> </Pressable> <Pressable style={styles.buttons} onPress={() => handleClick(2)} disabled={isLoading} > <Text style={styles.smiley} >😐</Text> </Pressable> <Pressable style={styles.buttons} onPress={() => handleClick(1)} disabled={isLoading} > <Text style={styles.smiley} >☹️</Text> </Pressable> </View> <StatusBar style="auto" /> </View> )} </View> ); } const styles = StyleSheet.create({ // 样式内容补充完整 });
额外优化建议
- 合并重复的
handleClickX函数为一个通用函数,减少代码冗余; - 添加按钮加载状态,防止用户重复点击触发多次请求;
- 对fetch请求的URL可以提取为常量,方便后续维护;
- 考虑使用
useCallback包裹handleClick函数,避免每次渲染都创建新函数(如果后续有性能问题的话)。
内容的提问来源于stack exchange,提问作者BettiGem
相关产品推荐
相关产品推荐

