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

React Native中useEffect内setPoints重复加1问题的最优修复方法

问题原因分析
  1. Animated实例重复创建:你在组件顶层直接new Animated.ValueXY,函数组件每次渲染都会重新创建这个实例,导致useEffect的依赖项theBallPosition频繁变化,每次渲染都会添加新的监听器。旧监听器虽然在cleanup时移除,但如果动画过程中组件重新渲染,就会存在多个监听器,动画结束时多个监听器同时触发,导致setPoints执行两次。
  2. 监听器帧回调重复触发:Animated的监听器会在每一帧触发,动画结束前的最后几帧可能出现多次value.y等于目标值的情况(尤其是线性动画),或者因为浮点数精度问题,导致条件判断多次成立。
解决方法

方法一:用动画完成回调替代帧监听器(推荐)

直接在Animated.timing的start方法里传入回调,动画完成时只会执行一次,这是处理动画结束逻辑的标准方式:

import { useRef } from 'react';
import { Animated, Easing } from 'react-native';

// 用useRef保存Animated实例,避免每次渲染重新创建
const theBallPosition = useRef(new Animated.ValueXY({x: 0, y: 0})).current;

const destinationY = distanceOfScreenTopToBigCircle || 0;

function movingBallFunction() {
  Animated.timing(theBallPosition, {
    toValue: {x: 0, y: destinationY},
    duration: movingBallTimeDuration,
    easing: Easing.linear,
    useNativeDriver: true,
  }).start(() => {
    // 动画完成时执行,只会触发一次
    setPoints(prevPoint => prevPoint + 1);
  });

  setShowHowToStartGameInstruction(false);
}

// 不需要原来的useEffect监听器了

方法二:修复监听器重复触发问题(如果一定要用监听器)

  1. 用useRef保存Animated实例,避免重复创建;
  2. 添加一个触发标志位,防止重复执行setPoints:
import { useRef, useEffect } from 'react';
import { Animated, Easing } from 'react-native';

const theBallPosition = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
// 保存是否已经触发过加分的标志
const hasAddedPoint = useRef(false);

const destinationY = distanceOfScreenTopToBigCircle || 0;

function movingBallFunction() {
  // 每次开始动画时重置标志位
  hasAddedPoint.current = false;
  
  Animated.timing(theBallPosition, {
    toValue: {x: 0, y: destinationY},
    duration: movingBallTimeDuration,
    easing: Easing.linear,
    useNativeDriver: true,
  }).start();

  setShowHowToStartGameInstruction(false);
}

useEffect(() => {
  const animationListener = (value) => {
    // 检查是否到达目标值,且未触发过加分
    if (value.y === distanceOfScreenTopToBigCircle && !hasAddedPoint.current) {
      hasAddedPoint.current = true;
      setPoints(prevPoint => prevPoint + 1);
    }
  };

  const listenerId = theBallPosition.addListener(animationListener);

  return () => {
    theBallPosition.removeListener(listenerId);
  };
}, [distanceOfScreenTopToBigCircle])

额外提示

避免用===直接比较浮点数(动画的value.y是浮点数),可以用范围判断代替,比如:

if (Math.abs(value.y - distanceOfScreenTopToBigCircle) < 0.1 && !hasAddedPoint.current) {
  // 执行逻辑
}

这样能避免因为浮点数精度问题导致的判断失效或重复触发。

内容的提问来源于stack exchange,提问作者Kingsley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 21:47:05