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

Expo管理的React Native Web应用中拖拽组件出现findDOMNode已弃用警告的问题排查

Expo管理的React Native Web应用中拖拽组件出现findDOMNode已弃用警告的问题排查

我看到你在Expo管理的React Native Web应用里,用react-native-gesture-handler和Reanimated实现拖拽填空功能时,遇到了findDOMNode弃用警告的棘手问题——要么警告弹窗跳个不停,要么拖拽功能直接失效,而且问题明显集中在DraggableWord组件里。这个场景在React 18+的严格模式下特别常见,核心原因是GestureDetector无法正确获取Animated.View的原生DOM节点,只能回退到已弃用的findDOMNode API来兜底。下面是具体的排查思路和可直接落地的修复方案:

问题根源拆解

你的DraggableWord里,GestureDetector直接包裹了Animated.View,并且给Animated.View绑定的是useAnimatedRef(专为Reanimated动画设计的引用),而非原生DOM ref。在Web环境下,react-native-gesture-handler需要获取原生DOM节点来监听鼠标/触摸事件,当它从AnimatedRef拿不到有效节点时,就会调用已被React 18弃用的findDOMNode,触发警告。

分步修复方案

我们要做的是让GestureDetector直接拿到原生DOM节点的引用,同时保留Reanimated动画所需的AnimatedRef,具体修改如下:

1. 给DraggableWord添加组合引用

在DraggableWord内部同时创建原生DOM ref和Animated ref,再通过一个组合函数把两者绑定到同一个Animated.View上,兼顾动画和事件监听的需求。

2. 给PanGesture指定监听的原生ref

通过PanGesture的.ref()方法明确指定要监听的原生DOM节点,彻底避免GestureDetector内部调用findDOMNode。

3. 确保依赖版本兼容(可选但推荐)

升级到最新版的依赖,能避免旧版本的Web兼容性bug:

  • react-native-gesture-handler@^2.12.0+
  • react-native-reanimated@^3.3.0+
  • Expo SDK 49+

修复后的完整DraggableWord组件

把你原有的DraggableWord替换成下面的代码即可:

const DraggableWord = ({ choice, answer, onDrop, dropZoneLayout, isAttempted, isCorrect, droppedWord, blankIndex, hoverStates }) => {
  const opacity = useSharedValue(1);
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  
  // 1. 创建原生DOM ref和Reanimated专用ref
  const nativeDomRef = useRef(null);
  const animatedRef = useAnimatedRef();
  
  // 组合ref:同时绑定到Animated.View,兼顾动画和事件监听
  const combinedRef = (node) => {
    nativeDomRef.current = node;
    animatedRef.current = node;
  };

  useEffect(() => {
    if (!isAttempted) {
      opacity.value = 1;
    }
  }, [isAttempted]);

  const panGesture = Gesture.Pan()
    .runOnJS(true)
    .enabled(!isAttempted)
    .ref(nativeDomRef) // 2. 明确指定Gesture监听的原生DOM节点
    .onUpdate((event) => {
      translateX.value = event.translationX;
      translateY.value = event.translationY;
      let isHovering = false;
      if (dropZoneLayout) {
        isHovering = (
          event.absoluteX > dropZoneLayout.x &&
          event.absoluteX < dropZoneLayout.x + dropZoneLayout.width &&
          event.absoluteY > dropZoneLayout.y &&
          event.absoluteY < dropZoneLayout.y + dropZoneLayout.height
        );
      }
      hoverStates.value = { ...hoverStates.value, [blankIndex]: isHovering };
    })
    .onEnd((event) => {
      hoverStates.value = { ...hoverStates.value, [blankIndex]: false };
      const wordIsOverDropZone = dropZoneLayout && (
        event.absoluteX > dropZoneLayout.x &&
        event.absoluteX < dropZoneLayout.x + dropZoneLayout.width &&
        event.absoluteY > dropZoneLayout.y &&
        event.absoluteY < dropZoneLayout.y + dropZoneLayout.height
      );
      if (wordIsOverDropZone) {
        onDrop(blankIndex, choice);
        if (choice === answer) {
          opacity.value = withTiming(0, { duration: 200 });
        } else {
          translateX.value = withSpring(0);
          translateY.value = withSpring(0);
        }
      } else {
        translateX.value = withSpring(0);
        translateY.value = withSpring(0);
      }
    });

  const animatedStyle = useAnimatedStyle(() => {
    let backgroundColor = theme.colors.grey;
    if (isAttempted) {
      if (choice === answer) {
        backgroundColor = theme.colors.successTwo;
      } else if (choice === droppedWord && !isCorrect) {
        backgroundColor = theme.colors.error;
      }
    }
    return {
      transform: [{ translateX: translateX.value }, { translateY: translateY.value }],
      zIndex: translateX.value !== 0 || translateY.value !== 0 ? 100 : 1,
      opacity: opacity.value,
      backgroundColor,
      padding: 10,
      margin: 5,
      borderRadius: 8,
      borderWidth: 1,
      borderColor: theme.colors.border,
    };
  });

  const styles = StyleSheet.create({
    wordText: { fontSize: 18, color: theme.colors.text }
  });

  // 3. 使用组合ref绑定Animated.View
  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View ref={combinedRef} style={animatedStyle}>
        <Text style={styles.wordText}>{choice}</Text>
      </Animated.View>
    </GestureDetector>
  );
};

验证修复效果

  1. 启动Expo Web的严格模式测试,确认findDOMNode弃用警告消失;
  2. 测试完整的拖拽流程:拖拽单词、悬停时的边框高亮、放下后的正确/错误状态反馈、复位动画等功能是否正常;
  3. 如果仍有异常,可以临时禁用React严格模式(仅用于验证),确认是否是其他代码逻辑的问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 11:09:31