如何解决Kendo React Grid空网格间拖拽失效问题?
问题分析与解决方案
核心错误点
- 事件绑定逻辑错误:你给空Grid提示元素绑定的
onDrop={dragStart}和onDragOver={dragEnd}完全不符合拖拽事件的处理逻辑——dragStart是拖拽开始时触发的函数,dragEnd是拖拽结束时的函数,空Grid需要的是处理拖拽悬停和放置完成的逻辑,且onDragOver必须阻止默认行为才能触发onDrop。 - Grid引用冲突:两个Grid共用同一个
gridref,导致组件引用混乱,可能影响拖拽相关的上下文判断。 - 空Grid无数据项时的逻辑缺失:原
reorder函数依赖目标Grid中存在的相对数据项来插入,但空Grid没有数据项,需要单独处理直接添加选中项的逻辑。
修正步骤
1. 拆分Grid引用
将两个Grid的ref分开,避免引用冲突:
const grid1Ref = React.useRef(null); const grid2Ref = React.useRef(null);
然后分别给两个Grid设置ref属性:
<Grid ref={grid1Ref} // 其他属性不变 > {/* ... */} </Grid> <Grid ref={grid2Ref} // 其他属性不变 > {/* ... */} </Grid>
2. 新增空Grid的放置处理函数
添加专门处理空Grid放置的逻辑,直接将选中项添加到目标Grid并从原Grid移除:
const handleDropOnEmptyGrid = (targetGrid: 'first' | 'second') => { let updatedGrid1 = [...gridData1]; let updatedGrid2 = [...gridData2]; // 从两个Grid中移除已选中的项 updatedGrid1 = removeDataItems(selection, updatedGrid1); updatedGrid2 = removeDataItems(selection, updatedGrid2); // 将选中项添加到目标空Grid if (targetGrid === 'first') { updatedGrid1 = [...updatedGrid1, ...selection]; } else { updatedGrid2 = [...updatedGrid2, ...selection]; } setGridData1(updatedGrid1); setGridData2(updatedGrid2); setActiveItem(null); };
3. 修正GridNoRecords的事件绑定
给空Grid的提示元素绑定正确的拖拽事件,注意onDragOver必须阻止默认行为:
// 第一个Grid的GridNoRecords <GridNoRecords> <div style={{ height: 350, backgroundColor: 'grey' }} onDragOver={(e) => { e.preventDefault(); // 必须阻止默认行为才能触发onDrop }} onDrop={() => handleDropOnEmptyGrid('first')} > Drag items here </div> </GridNoRecords> // 第二个Grid的GridNoRecords <GridNoRecords> <div style={{ height: 350, backgroundColor: 'grey' }} onDragOver={(e) => { e.preventDefault(); // 必须阻止默认行为才能触发onDrop }} onDrop={() => handleDropOnEmptyGrid('second')} > Drag items here </div> </GridNoRecords>
4. 调整上下文引用(可选)
如果你的GridContext用于拖拽相关逻辑,需要将其改为分别提供两个Grid的ref,或者根据实际使用场景调整上下文内容,避免引用混乱。
修正后的完整App组件代码
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Grid, GridNoRecords, GridColumn as Column, } from '@progress/kendo-react-grid'; import { DragAndDrop } from '@progress/kendo-react-common'; import { DraggableRow } from './draggable-row'; import { SelectionCell } from './selection-cell'; import { DragHint } from './drag-hint'; import products from './products.json'; import { Product } from './interfaces'; export const ReorderContext = React.createContext<{ reorder: (dataItem: Product, direction: 'before' | 'after' | null) => void; dragStart: (dataItem: Product) => void; dragEnd: (dataItem?: Product) => void; }>({ reorder: () => {}, dragStart: () => {}, dragEnd: () => {} }); export const SelectionContext = React.createContext<[Product[], any]>([ [], () => {}, ]); export const IsSelectedContext = React.createContext<boolean>(false); export const DragHintContext = React.createContext<React.RefObject<HTMLElement> | null>(null); export const GridContext = React.createContext<React.RefObject<Grid> | null>( null ); const App = () => { const grid1Ref = React.useRef(null); const grid2Ref = React.useRef(null); const hint = React.useRef(null); const [gridData1, setGridData1] = React.useState(products.slice(0, 0)); const [gridData2, setGridData2] = React.useState(products.slice(31, 60)); const [selection, setSelection] = React.useState<Product[]>([]); const [activeItem, setActiveItem] = React.useState<Product | null>(null); const reorder = (relativeDataItem: any, direction: 'before' | 'after') => { let firstGridData = gridData1.slice(); let secondGridData = gridData2.slice(); const insertToFirst = firstGridData.some( (i) => i.ProductID === relativeDataItem.ProductID ); const insertToSecond = secondGridData.some( (i) => i.ProductID === relativeDataItem.ProductID ); firstGridData = removeDataItems(selection, firstGridData); secondGridData = removeDataItems(selection, secondGridData); if (insertToFirst) { firstGridData = insertDataItems( selection, relativeDataItem, direction, firstGridData ); } if (insertToSecond) { secondGridData = insertDataItems( selection, relativeDataItem, direction, secondGridData ); } setGridData1(firstGridData); setGridData2(secondGridData); setActiveItem(null); }; const insertDataItems = ( itemsToInsert, relativeDataItem, direction, collection ) => { let reorderedData = collection.slice(); let nextIndex = reorderedData.findIndex( (p) => p.ProductID === relativeDataItem.ProductID ); reorderedData.splice( Math.max(nextIndex + (direction === 'before' ? 0 : 1), 0), 0, ...itemsToInsert ); return reorderedData; }; const removeDataItems = (itemsToRemove, collection) => { if ( collection.some((i) => itemsToRemove.some((s) => s.ProductID === i.ProductID) ) ) { const newData = collection.filter( (i) => !itemsToRemove.some((s) => s.ProductID === i.ProductID) ); return newData; } return collection; }; const dragStart = (dataItem: any) => { setActiveItem(dataItem); }; const dragEnd = () => { setActiveItem(null); }; const handleDropOnEmptyGrid = (targetGrid: 'first' | 'second') => { let updatedGrid1 = [...gridData1]; let updatedGrid2 = [...gridData2]; updatedGrid1 = removeDataItems(selection, updatedGrid1); updatedGrid2 = removeDataItems(selection, updatedGrid2); if (targetGrid === 'first') { updatedGrid1 = [...updatedGrid1, ...selection]; } else { updatedGrid2 = [...updatedGrid2, ...selection]; } setGridData1(updatedGrid1); setGridData2(updatedGrid2); setActiveItem(null); }; return ( <GridContext.Provider value={null}> <DragHintContext.Provider value={hint}> <ReorderContext.Provider value={{ reorder, dragStart, dragEnd }}> <SelectionContext.Provider value={[selection, setSelection]}> <DragAndDrop> <Grid ref={grid1Ref} style={{ height: '400px' }} data={gridData1} dataItemKey={'ProductID'} rowRender={(row, rowProps) => ( <DraggableRow elementProps={row.props} {...rowProps} /> )} > <GridNoRecords> <div style={{ height: 350, backgroundColor: 'grey' }} onDragOver={(e) => e.preventDefault()} onDrop={() => handleDropOnEmptyGrid('first')} > Drag items here </div> </GridNoRecords> <Column title="" width="80px" cell={SelectionCell} /> <Column field="ProductID" title="ID" width="60px" /> <Column field="ProductName" title="Name" width="200px" /> <Column field="Category.CategoryName" title="CategoryName" /> <Column field="UnitPrice" title="Price" width="80px" /> <Column field="UnitsInStock" title="In stock" width="80px" /> </Grid> <hr /> <Grid ref={grid2Ref} style={{ height: '400px' }} data={gridData2} dataItemKey={'ProductID'} rowRender={(row, rowProps) => ( <DraggableRow elementProps={row.props} {...rowProps} /> )} > <GridNoRecords> <div style={{ height: 350, backgroundColor: 'grey' }} onDragOver={(e) => e.preventDefault()} onDrop={() => handleDropOnEmptyGrid('second')} > Drag items here </div> </GridNoRecords> <Column title="" width="80px" cell={SelectionCell} /> <Column field="ProductID" title="ID" width="60px" /> <Column field="ProductName" title="Name" width="200px" /> <Column field="Category.CategoryName" title="CategoryName" /> <Column field="UnitPrice" title="Price" width="80px" /> <Column field="UnitsInStock" title="In stock" width="80px" /> </Grid> <DragHint ref={hint} portal={grid1Ref} className="k-card" style={{ display: activeItem ? undefined : 'none' }} > {activeItem && activeItem.ProductName} {selection.length > 1 && ( <div style={{ position: 'absolute', pointerEvents: 'none', bottom: 0, right: 0, background: 'red', padding: 8, width: 32, color: 'white', borderRadius: '50%', transform: 'translate(50%, 50%)', }} > +{selection.length - 1} </div> )} </DragHint> </DragAndDrop> </SelectionContext.Provider> </ReorderContext.Provider> </DragHintContext.Provider> </GridContext.Provider> ); }; ReactDOM.render(<App />, document.querySelector('my-app'));
关键说明
onDragOver必须调用e.preventDefault():浏览器默认会阻止拖拽放置行为,只有阻止默认行为后,onDrop事件才会触发。- 空Grid的放置逻辑独立于原有
reorder函数:因为空Grid没有可参考的数据项,直接将选中项追加到目标Grid即可。 - 拆分Grid ref避免引用冲突:确保每个Grid的引用独立,不会影响拖拽相关的上下文判断。
内容的提问来源于stack exchange,提问作者Pathar Lamak
相关产品推荐
相关产品推荐

