Next.js使用react-moveable/react-selecto时onDragGroup失效如何解决
问题背景
- 技术栈:React v18.1.0、Next.js v12.1.6,使用
react-moveable、react-selecto实现画布元素拖拽多选能力 - 异常表现:单选元素拖拽时
onDrag事件正常触发,多选元素后触发组拖拽时,onDragGroup系列事件完全不响应 - 初始怀疑方向:是否由Next.js的SSR渲染机制导致兼容性问题
附原业务实现代码:
import React, { useRef, useState } from "react"; import Moveable from "react-moveable"; import Selecto from "react-selecto"; export default function MoveableProvider() { const [targets, setTargets] = useState([]); const [frameMap] = useState(() => new Map()); const selectoRef = useRef(null); const moveableRef = useRef(null); return ( <div className="container"> <Moveable ref={moveableRef} draggable={true} target={targets} onClickGroup={(e) => { selectoRef.current.clickTarget(e.inputEvent, e.inputTarget); }} onDrag={(e) => { const target = e.target; const frame = frameMap.get(target); frame.translate = e.beforeTranslate; target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`; }} onDragStart={(e) => { const target = e.target; if (!frameMap.has(target)) { frameMap.set(target, { translate: [0, 0], }); } const frame = frameMap.get(target); e.set(frame.translate); }} onDragGroupStart={(e) => { e.events.forEach((ev) => { const target = ev.target; if (!frameMap.has(target)) { frameMap.set(target, { translate: [0, 0], }); } const frame = frameMap.get(target); ev.set(frame.translate); }); }} onDragGroup={(e) => { e.events.forEach((ev) => { const target = ev.target; const frame = frameMap.get(target); frame.translate = ev.beforeTranslate; target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`; }); }} /> <Selecto ref={selectoRef} dragContainer={".elements"} selectableTargets={[".selecto-area .cube"]} hitRate={0} selectByClick={true} selectFromInside={false} toggleContinueSelect={["shift"]} ratio={0} onDragStart={(e) => { const moveable = moveableRef.current; const target = e.inputEvent.target; k; if ( moveable.isMoveableElement(target) || targets.some((t) => t === target || t.contains(target)) ) { e.stop(); } }} onSelectEnd={(e) => { const moveable = moveableRef.current; setTargets(e.selected); if (e.isDragStart) { e.inputEvent.preventDefault(); setTimeout(() => { moveable.dragStart(e.inputEvent); }); } }} ></Selecto> <div className="elements selecto-area" style={{ display: "flex" }}> <div className="cube target" style={{ width: 250, height: 200, background: "green", }} ></div> <div className="cube target" style={{ width: 250, height: 200, background: "yellow", }} ></div> <div className="cube target" style={{ width: 250, height: 200, background: "aqua", }} ></div> <div className="cube target" style={{ width: 250, height: 200, background: "red", }} ></div> </div> <div className="empty elements"></div> </div> ); }
问题根因
该异常由三个问题共同导致,SSR机制确实是诱因之一:
- SSR渲染兼容性问题:
react-moveable和react-selecto强依赖浏览器端DOM API与全局window对象,Next.js默认服务端预渲染时会生成不匹配的组件实例,导致多目标场景下的组事件监听绑定失效 - 代码语法错误:Selecto组件的
onDragStart回调中存在无意义的孤立语句k;,多选拖拽触发时会直接抛出ReferenceError: k is not defined,中断后续事件流,导致组拖拽逻辑完全无法执行 - 组件配置缺失:Moveable组件未开启组拖拽相关的必要属性,传入多目标数组时不会自动切换到组拖拽模式,仍然走单元素拖拽逻辑
修复方案
按以下步骤调整即可解决问题:
- 禁用第三方组件SSR加载:使用Next.js提供的动态导入能力,将两个依赖包改为仅客户端渲染,替换原有静态导入语句:
import dynamic from "next/dynamic"; // 替换原有的import Moveable/Selecto语句 const Moveable = dynamic(() => import("react-moveable"), { ssr: false }); const Selecto = dynamic(() => import("react-selecto"), { ssr: false });
- 清除语法错误:删除Selecto的
onDragStart回调中孤立的k;语句,避免运行时报错中断事件 - 补充Moveable组拖拽配置:在Moveable组件上新增两个属性,开启多目标组拖拽能力:
<Moveable ref={moveableRef} draggable={true} groupable={true} // 新增:开启组拖拽模式 individualGroupable={true} // 新增:支持单/多目标拖拽模式自动切换 target={targets} // 其余原有回调逻辑保持不变 />
- (可选)适配React 18严格模式:如果项目开启了React 18严格模式,将Moveable、Selecto的ref实例校验逻辑放到
useEffect中执行,避免严格模式双渲染导致的ref引用错乱
修复完成后,多选元素拖拽时onDragGroupStart、onDragGroup事件均可正常触发,单元素拖拽逻辑不受影响。
内容的提问来源于stack exchange,提问作者sorena-paydar
相关产品推荐
相关产品推荐

