React Native中useEffect内setPoints重复加1问题的最优修复方法
问题原因分析
- Animated实例重复创建:你在组件顶层直接
new Animated.ValueXY,函数组件每次渲染都会重新创建这个实例,导致useEffect的依赖项theBallPosition频繁变化,每次渲染都会添加新的监听器。旧监听器虽然在cleanup时移除,但如果动画过程中组件重新渲染,就会存在多个监听器,动画结束时多个监听器同时触发,导致setPoints执行两次。 - 监听器帧回调重复触发:
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监听器了
方法二:修复监听器重复触发问题(如果一定要用监听器)
- 用
useRef保存Animated实例,避免重复创建; - 添加一个触发标志位,防止重复执行
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
相关产品推荐
相关产品推荐

