React DnD嵌套组件:子组件可设props但父组件拖放事件失效
首先得明确一个React核心原则:组件的props是只读的,不能直接修改。你提到父组件在drop事件里“无法设置props”,大概率是因为你在尝试直接修改父组件的props——这本身就不符合React的设计逻辑,而子组件能生效,可能是因为子组件在操作自己的内部state,而非props。
结合你的代码结构,我来一步步帮你解决这个问题:
1. 先梳理你的代码矛盾点
你的结构是ComponentA作为父容器,内部渲染多个ComponentB,两者都是DropTarget,且共用相同的drop逻辑。但你给ComponentA传的是handleRootDropEvent,给ComponentB传的是handleDropEvent——如果这两个函数逻辑一致,建议统一成一个,避免重复代码,也能减少作用域问题。
2. 核心解决方案:统一状态管理
所有分类数据(this.state.categories)应该由最上层的父组件(也就是渲染ComponentA的那个组件)来维护,不管是ComponentA还是ComponentB触发drop事件,都通知这个最上层组件去更新state,再通过props把新数据传递给所有子组件。这样既能避免直接修改props的问题,也能保证数据一致性。
步骤1:把drop逻辑统一到最上层父组件
// 最上层的父组件,也就是你当前持有categories state的组件 class CategoryContainer extends React.Component { state = { categories: [] // 你的分类数据 }; // 统一处理所有drop事件的函数 handleDrop = (dropResult, sourceProps) => { // 根据触发drop的组件类型(父/子)和拖拽数据,更新分类 const updatedCategories = this.updateCategoryStructure( this.state.categories, dropResult, sourceProps ); // 用setState更新状态,React会自动把新数据传递给子组件 this.setState({ categories: updatedCategories }); }; // 具体的分类更新逻辑,根据你的业务需求调整 updateCategoryStructure = (categories, dropResult, sourceProps) => { const draggedItem = dropResult; // 区分是ComponentA(根容器)还是ComponentB(子分类)触发的drop if (sourceProps.componentType === 'root') { // 拖拽到根容器:把分类添加到根级别 return [...categories, { ...draggedItem, level: 0, parentCat: null }]; } else { // 拖拽到子分类:把分类添加到当前分类下 return categories.map(cat => { if (cat.id === sourceProps.categoryId) { return { ...cat, children: [...(cat.children || []), { ...draggedItem, level: sourceProps.level + 1, parentCat: cat.id }] }; } return cat; }); } }; render() { return ( <ComponentA dropEvent={this.handleDrop} componentType="root" // 标记是根容器组件 > <p>Displayed Categories </p> {this.state.categories.map((c, idx) => <ComponentB key={idx} parentCat={null} thisCat={c} level={c.level} dropEvent={this.handleDrop} componentType="child" // 标记是子分类组件 categoryId={c.id} // 传递当前分类ID,方便更新时定位 /> )} </ComponentA> ); } }
步骤2:在DropTarget组件中正确传递事件参数
不管是ComponentA还是ComponentB,作为DropTarget,它们的drop函数需要把拖拽结果和自身的props传递给上层的handleDrop函数,这样上层组件就能知道是哪个组件触发的drop,从而做出正确的更新:
// ComponentA的DropTarget封装 import { DropTarget } from 'react-dnd'; const ComponentADropTarget = DropTarget( 'CATEGORY', // 拖拽类型,要和拖拽源的类型一致 { drop(props, monitor) { const dropResult = monitor.getItem(); // 获取拖拽的内容 // 调用上层传递的dropEvent,把拖拽结果和自身props传过去 props.dropEvent(dropResult, props); } }, (connect) => ({ connectDropTarget: connect.dropTarget() }) )(({ connectDropTarget, children, ...props }) => { return connectDropTarget( <div {...props}> {children} </div> ); }); // ComponentB的DropTarget封装(逻辑和ComponentA一致) const ComponentBDropTarget = DropTarget( 'CATEGORY', { drop(props, monitor) { const dropResult = monitor.getItem(); props.dropEvent(dropResult, props); } }, (connect) => ({ connectDropTarget: connect.dropTarget() }) )(({ connectDropTarget, thisCat, ...props }) => { return connectDropTarget( <div {...props}> {/* 这里渲染你的子分类内容 */} {thisCat.name} </div> ); });
3. 为什么之前父组件无法“设置props”?
你之前的问题本质是:
- 父组件(
ComponentA)的drop函数里尝试直接修改自身的props,但React中props是只读的,无法直接赋值修改 - 子组件(
ComponentB)可能是在修改自己的内部state,所以能看到局部效果,但这种方式会导致状态分散,不同组件的数据无法同步
通过把状态统一维护在最上层父组件,所有drop操作都触发父组件的setState,就能解决这个问题,同时保证所有组件的数据都是同步的。
内容的提问来源于stack exchange,提问作者Daniel Thompson

