You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 19:14:55