React-DnD实现4x4数字拼图拖拽失效问题求助
4x4数字拼图拖拽问题修复
核心错误点
1. isDraggable方法逻辑缺陷
找到空白格后,原代码只要第一个条件不满足就立即返回false,导致仅空白格左侧的方块会被判定为可拖拽,上、下、右三个方向的方块会被错误判定为不可拖拽。
2. handleDrag方法参数传递错误
调用isDraggable时错误传入了toIndex[0]作为列参数,应该传递拖拽起始位置的列索引fromIndex[1]。
3. 直接修改State数组
shuffle2darray方法直接修改了this.state.numbers,违反React状态不可变原则,会导致状态更新异常。
4. Tile组件Ref绑定冲突
同时在内部div和外部div绑定drag,导致拖拽逻辑混乱。
修复后的完整代码
Board类(Number_puzzle.js)
import React from 'react'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { DndProvider } from 'react-dnd'; import { Tile } from './Tile' import './Number_puzzle.css'; export class Number_puzzle extends React.Component { constructor(props){ super(props); // 初始化时复制数组,避免直接修改原数据 const initialNumbers = [ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,-1] ]; this.shuffle2darray(initialNumbers); this.state = { numbers: initialNumbers }; console.log("shuffled!") } shuffle2darray(numbers){ // 复制数组避免修改原数据 const arr = JSON.parse(JSON.stringify(numbers)); for (let i = arr.length - 1; i > 0; i--) { for (let j = arr[i].length - 1; j > 0; j--) { const randomI = Math.floor(Math.random() * (i + 1)); const randomJ = Math.floor(Math.random() * (j + 1)); // 交换元素 [arr[i][j], arr[randomI][randomJ]] = [arr[randomI][randomJ], arr[i][j]]; } } // 将洗牌后的数据赋值回原数组 Object.assign(numbers, arr); } isDraggable(tileRow, tileCol){ let isAdjacent = false; for (let i = 0; i < this.state.numbers.length; i++) { for (let j = 0; j < this.state.numbers[i].length; j++) { if (this.state.numbers[i][j] === -1) { // 检查所有四个相邻方向,只要满足一个就设为true const isLeft = tileRow === i && tileCol === j - 1; const isRight = tileRow === i && tileCol === j + 1; const isTop = tileRow === i - 1 && tileCol === j; const isBottom = tileRow === i + 1 && tileCol === j; isAdjacent = isLeft || isRight || isTop || isBottom; // 找到空白格后直接跳出循环,无需继续遍历 break; } } if (isAdjacent) break; } return isAdjacent; } handleDrag(fromIndex, toIndex) { // 检查拖拽起始位置是否和空白格相邻 if (this.isDraggable(fromIndex[0], fromIndex[1])) { // 深拷贝二维数组,避免修改原state const numbers = JSON.parse(JSON.stringify(this.state.numbers)); numbers[toIndex[0]][toIndex[1]] = numbers[fromIndex[0]][fromIndex[1]]; numbers[fromIndex[0]][fromIndex[1]] = -1; this.setState({ numbers }); } } render() { return ( <DndProvider backend={HTML5Backend}> <div className="grid-container"> {this.state.numbers.map((row, i) => ( <React.Fragment key={i}> {row.map((number, j) => ( <div key={`${i}${j}`}> <Tile number={number} position={[i, j]} isAdjacent={this.isDraggable(i, j)} onDrop={this.handleDrag.bind(this)} /> </div> ))} </React.Fragment> ))} </div> </DndProvider> ); } }
Tile类(Tile.js)
import React from 'react'; import { useDrag, useDrop } from 'react-dnd'; export function Tile({ number, position, isAdjacent, onDrop }) { const [{ isDragging }, drag] = useDrag({ type: 'tile', item: { position }, canDrag: isAdjacent && number !== -1, // 空白格不可拖拽 collect: (monitor) => ({ isDragging: !!monitor.isDragging(), }), }); const [{ isOver }, drop] = useDrop({ accept: 'tile', canDrop: () => number === -1, // 仅空白格可接受拖拽 drop: (item) => onDrop(item.position, position), collect: (monitor) => ({ isOver: !!monitor.isOver(), }), }); const style = { opacity: isDragging ? 0.5 : 1, backgroundColor: isAdjacent ? 'lightgreen' : 'white', border: isOver ? '2px dashed black' : 'none', cursor: isAdjacent ? 'move' : 'default' }; // 统一绑定ref到容器div const ref = (node) => { drag(node); drop(node); }; return ( <div ref={ref} style={style} > {number !== -1 && <div>{number}</div>} </div> ); }
关键修复说明
- 修正
isDraggable逻辑:遍历找到空白格后,同时检查四个相邻方向,只要满足任一条件就返回true,避免提前返回false。 - 修复
handleDrag参数:正确传递拖拽起始位置的行列索引到isDraggable。 - 遵守状态不可变原则:洗牌和状态更新时都深拷贝数组,避免直接修改state。
- 简化Tile组件Ref绑定:将
drag和drop统一绑定到容器div,移除冗余的onDragStart和内部div的ref,避免逻辑冲突。 - 优化拖拽权限判断:在Tile的
canDrag中明确禁止空白格本身被拖拽,canDrop仅允许空白格接受拖拽,逻辑更清晰。
内容的提问来源于stack exchange,提问作者bryan lee
相关产品推荐
相关产品推荐

