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

useLayoutEffect触发频繁导致单次PointerTAP事件触发两次pointerClick的问题咨询

解决Babylon.js POINTERTAP事件单次点击触发两次回调的问题

我明白你现在的困境——用useLayoutEffect绑定Babylon.js的点击事件,因为依赖buildings和activeBuilding导致单次点击触发两次pointerClick,换成useEffect又有延迟问题。其实核心问题出在不必要的频繁监听绑定上,咱们一步步来解决:

问题根源分析

你当前的useLayoutEffect依赖了activeBuilding,这意味着每次点击修改activeBuilding后,组件会重新渲染,useLayoutEffect会立刻执行:先移除旧的监听,再添加新的监听。虽然理论上旧监听会被清理,但在同步执行的useLayoutEffect流程中,可能因为Babylon.js事件系统的特性,或者闭包导致的旧回调残留,出现单次点击触发两次回调的情况。

而你需要依赖buildings和activeBuilding的原因,是希望pointerClick能访问到最新的状态值——这可以通过useCallback来解决,不用把状态直接放进useLayoutEffect的依赖里。

解决方案

我们用useCallback包裹pointerClick函数,让它的引用只在依赖的状态/函数变化时更新,然后把useLayoutEffect的依赖简化为scene和pointerClick,这样就不会因为activeBuilding的每次变化都重新绑定监听了。

修改后的代码示例

// 用useCallback包裹pointerClick,确保引用稳定
const pointerClick = useCallback(() => {
  if (!scene) return;
  
  const pickInfo = scene.pick(scene.pointerX, scene.pointerY);
  if (buildings.length === 0 && activeBuilding === -1) {
    setActiveBuilding(0);
  }
  if (activeBuilding !== -1) {
    if (pickInfo?.pickedMesh?.metadata?.measurement?.type === 'handle') {
      handleDeletePoint(
        pickInfo.pickedMesh.metadata.measurement.id,
        activeBuilding
      );
    } else if (pickInfo?.pickedMesh?.name === 'measureOutline') {
      setActiveBuilding(-1);
    } else if (
      !pickInfo?.pickedMesh?.metadata?.measurement && pickInfo?.pickedPoint
    ) {
      handleAddPoint(pickInfo.pickedPoint, activeBuilding);
    }
  }
}, [scene, buildings, activeBuilding, handleDeletePoint, handleAddPoint]); // 列出所有用到的外部依赖

useLayoutEffect(() => {
  if (!scene) return;
  
  // 绑定监听,只在scene或pointerClick变化时执行
  const obs = scene.onPointerObservable.add((pointerInfoEvent) => {
    if (pointerInfoEvent.type === PointerEventTypes.POINTERTAP) {
      pointerClick();
    }
  });

  return () => {
    // 确保旧监听被正确移除
    scene.onPointerObservable.remove(obs);
  };
}, [scene, pointerClick]); // 仅依赖scene和优化后的pointerClick

return <Measure />;

为什么这个方案有效

  1. useCallback的作用:它会缓存pointerClick的引用,只有当数组里的依赖(buildings、activeBuilding等)变化时,才会生成新的函数。这样既保证了pointerClick能访问到最新的状态,又避免了不必要的函数重建。
  2. 简化useLayoutEffect依赖:现在useLayoutEffect只会在scene初始化/变化,或者pointerClick的依赖更新时才重新绑定监听,不会因为activeBuilding的每次点击修改就重复执行,从根源上避免了重复触发的问题。
  3. 保留useLayoutEffect的优势:依旧保持同步执行的时机,确保监听在DOM更新后、浏览器绘制前就绑定完成,不会出现useEffect那种点击两次才生效的延迟问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 20:38:09