React Native中onPanResponderMove无法获取更新后state问题求助
这个问题我之前也碰到过,核心原因是闭包陷阱导致的,咱们一步步来拆解和解决:
为什么会出现这个问题?
你用useRef创建的panResponder只会在组件第一次渲染时初始化一次,之后即使组件状态更新,这个panResponder实例也不会重新创建。而handleonPanResponderMove函数是在第一次渲染时被捕获到panResponder里的,它引用的是当时的abc状态(初始值0)。后续你通过setabc更新状态时,组件会重新渲染,但旧的handleonPanResponderMove函数依然保留着对旧abc值的引用,所以每次触发它时,打印的都是初始的0。
解决方案
这里有几种靠谱的解决方式,按推荐程度排序:
1. 使用函数式更新状态
React的setState(包括useState返回的更新函数)支持传入一个函数,这个函数会接收上一次的状态值作为参数,你可以基于这个值来计算新状态。这样就不需要依赖当前闭包里的abc值,自然能避开闭包陷阱。
修改你的handleonPanResponderMove函数:
const handleonPanResponderMove = (evt, gestureState) => { const { dy } = gestureState; console.log('in handleonPanResponderMove:', gestureState); if (dy < 0) { // 使用函数式更新,基于前一个状态计算新值 setabc(prevAbc => prevAbc + 1); } else if (dy > 0) { setabc(prevAbc => prevAbc - 1); } };
如果你需要在函数里获取最新的abc值来做其他逻辑,那可以看第二种方案。
2. 用useRef保存最新状态
创建一个ref来同步保存abc的最新值,每次状态更新时同步更新这个ref,这样在handleonPanResponderMove里就能通过ref.current获取到最新的状态了。
修改组件代码:
import React, { useRef, useState, useEffect } from 'react'; import { View, PanResponder } from 'react-native'; function SimpleWrapper(props) { const { children } = props; const [abc, setabc] = useState(0); // 创建ref保存最新的abc状态 const abcRef = useRef(abc); // 每次abc更新时,同步更新ref useEffect(() => { abcRef.current = abc; }, [abc]); console.log('SimpleWrapper', abc); const panResponder = useRef( PanResponder.create({ onStartShouldSetPanResponder: (evt, gestureState) => handleShouldSetPanResponder(evt, gestureState), onMoveShouldSetPanResponder: (evt, gestureState) => handleShouldSetPanResponder(evt, gestureState), onPanResponderMove: (evt, gestureState) => handleonPanResponderMove(evt, gestureState), onPanResponderTerminationRequest: () => {} }) ).current; const handleShouldSetPanResponder = (evt, gestureState) => { const { dy } = gestureState; // 这里注意你代码里的gestureConfig未定义,替换成具体阈值数值 const isvalid = evt.nativeEvent.touches.length === 1 && Math.abs(dy) > 5; return isvalid; }; const handleonPanResponderMove = (evt, gestureState) => { const { dy } = gestureState; // 现在可以通过abcRef.current获取最新的状态值 console.log('in handleonPanResponderMove:', abcRef.current, gestureState); if (dy < 0) { setabc(prev => prev + 1); } else if (dy > 0) { setabc(prev => prev - 1); } }; return ( <View {...panResponder.panHandlers} style={}> {children} </View> ); } export default SimpleWrapper;
3. 依赖状态重新创建PanResponder(不推荐)
如果上面两种方案都不适合,你也可以让panResponder依赖abc状态,每次abc变化时重新创建PanResponder实例。但这种方式会带来额外的性能开销,因为每次状态更新都要重新创建PanResponder,所以一般不推荐:
// 把useRef改成useMemo,依赖abc const panResponder = useMemo(() => PanResponder.create({ onStartShouldSetPanResponder: (evt, gestureState) => handleShouldSetPanResponder(evt, gestureState), onMoveShouldSetPanResponder: (evt, gestureState) => handleShouldSetPanResponder(evt, gestureState), onPanResponderMove: (evt, gestureState) => handleonPanResponderMove(evt, gestureState), onPanResponderTerminationRequest: () => {} }), [abc] // 依赖abc,每次abc变化时重新创建 );
总结
最推荐的是第一种函数式更新的方式,既简单又高效;如果需要在回调里读取最新状态,就用第二种useRef同步状态的方案。
内容的提问来源于stack exchange,提问作者varun

