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 />;
为什么这个方案有效
useCallback的作用:它会缓存pointerClick的引用,只有当数组里的依赖(buildings、activeBuilding等)变化时,才会生成新的函数。这样既保证了pointerClick能访问到最新的状态,又避免了不必要的函数重建。- 简化
useLayoutEffect依赖:现在useLayoutEffect只会在scene初始化/变化,或者pointerClick的依赖更新时才重新绑定监听,不会因为activeBuilding的每次点击修改就重复执行,从根源上避免了重复触发的问题。 - 保留
useLayoutEffect的优势:依旧保持同步执行的时机,确保监听在DOM更新后、浏览器绘制前就绑定完成,不会出现useEffect那种点击两次才生效的延迟问题。
内容的提问来源于stack exchange,提问作者pcace
相关产品推荐
相关产品推荐

