gpt4 book ai didi

javascript - 在 React DnD drop 中获取 DOM 中的元素位置?

转载 作者:数据小太阳 更新时间:2023-10-29 04:45:17 26 4
gpt4 key购买 nike

我正在使用 React DnD 和 Redux(使用 Kea)构建一个表单生成器。我的拖放部分工作得很好,我已经设法在元素掉落时分派(dispatch)一个 Action ,然后我使用分派(dispatch)改变的状态渲染构建器。但是,为了以正确的顺序呈现元素,我(认为我)需要保存丢弃的元素相对于它的 sibling 的位置,但我无法找出任何不是绝对疯狂的东西。我已经尝试过使用 refs 并使用唯一 ID 查询 DOM(我知道我不应该这样做),但是这两种方法都感觉很糟糕,甚至都不起作用。

这是我的应用程序结构的简化表示:

@DragDropContext(HTML5Backend)
@connect({ /* redux things */ })
<Builder>
<Workbench tree={this.props.tree} />
<Sidebar fields={this.props.field}/>
</Builder>

工作台:

const boxTarget = {
drop(props, monitor, component) {
const item = monitor.getItem()
console.log(component, item.unique, component[item.unique]); // last one is undefined
window.component = component; // doing it manually works, so the element just isn't in the DOM yet

return {
key: 'workbench',
}
},
}

@DropTarget(ItemTypes.FIELD, boxTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}))
export default class Workbench extends Component {
render() {
const { tree } = this.props;
const { canDrop, isOver, connectDropTarget } = this.props

return connectDropTarget(
<div className={this.props.className}>
{tree.map((field, index) => {
const { key, attributes, parent, unique } = field;
if (parent === 'workbench') { // To render only root level nodes. I know how to render the children recursively, but to keep things simple...
return (
<Field
unique={unique}
key={key}
_key={key}
parent={this} // I'm passing the parent because the refs are useless in the Field instance (?) I don't know if this is a bad idea or not
/>
);
}

return null;
}).filter(Boolean)}
</div>,
)


// ...

字段:

const boxSource = {
beginDrag(props) {
return {
key: props._key,
unique: props.unique || shortid.generate(),
attributes: props.attributes,
}
},

endDrag(props, monitor) {
const item = monitor.getItem()
const dropResult = monitor.getDropResult()

console.log(dropResult);

if (dropResult) {
props.actions.onDrop({
item,
dropResult,
});
}
},
}

@connect({ /* redux stuff */ })
@DragSource(ItemTypes.FIELD, boxSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}))
export default class Field extends Component {
render() {
const { TagName, title, attributes, parent } = this.props
const { isDragging, connectDragSource } = this.props
const opacity = isDragging ? 0.4 : 1

return connectDragSource(
<div
className={classes.frame}
style={{opacity}}
data-unique={this.props.unique || false}
ref={(x) => parent[this.props.unique || this.props.key] = x} // If I save the ref to this instance, how do I access it in the drop function that works in context to boxTarget & Workbench?
>
<header className={classes.header}>
<span className={classes.headerName}>{title}</span>
</header>
<div className={classes.wrapper}>
<TagName {...attributes} />
</div>
</div>
)
}
}

边栏不是很相关。

我的状态是一个平面数组,由可用于呈现字段的对象组成,因此我根据 DOM 中的元素位置对其重新排序。

[
{
key: 'field_type1',
parent: 'workbench',
children: ['DAWPNC'], // If there's more children, "mutate" this according to the DOM
unique: 'AWJOPD',
attributes: {},
},
{
key: 'field_type2',
parent: 'AWJOPD',
children: false,
unique: 'DAWPNC',
attributes: {},
},
]

这个问题的相关部分围绕着

const boxTarget = {
drop(props, monitor, component) {
const item = monitor.getItem()
console.log(component, item.unique, component[item.unique]); // last one is undefined
window.component = component; // doing it manually works, so the element just isn't in the DOM yet

return {
key: 'workbench',
}
},
}

我想我只是得到了对元素的引用不知何故,但它似乎还不存在于 DOM 中。如果我尝试使用 ReactDOM 进行破解,情况是一样的:

 // still inside the drop function, "works" with the timeout, doesn't without, but this is a bad idea
setTimeout(() => {
const domNode = ReactDOM.findDOMNode(component);
const itemEl = domNode.querySelector(`[data-unique="${item.unique}"]`);
const parentEl = itemEl.parentNode;

const index = Array.from(parentEl.children).findIndex(x => x.getAttribute('data-unique') === item.unique);

console.log(domNode, itemEl, index);
});

我如何实现我想要的?

为我对分号的不一致使用表示歉意,我不知道我想从中得到什么。 我讨厌他们。

最佳答案

我认为这里的关键是认识到 Field 组件既可以是 DragSource 也可以是 DropTarget。然后我们可以定义一组标准的掉落类型,这些类型会影响状态的变化方式。

const DropType = {
After: 'DROP_AFTER',
Before: 'DROP_BEFORE',
Inside: 'DROP_INSIDE'
};

AfterBefore 将允许重新排序字段,而 Inside 将允许嵌套字段(或放入工作台)。

现在,处理任何掉落的 Action 创建者将是:

const drop = (source, target, dropType) => ({
type: actions.DROP,
source,
target,
dropType
});

它只需要源对象和目标对象,以及发生的掉落类型,然后将其转化为状态突变。

放置类型实际上只是目标边界、放置位置和(可选)拖动源的函数,所有这些都在特定 DropTarget 类型的上下文中:

(bounds, position, source) => dropType

应该为每种类型的 DropTarget 支持定义此函数。这将允许每个 DropTarget 支持一组不同的放置类型。例如,Workbench 只知道如何在其内部放置某些东西,而不是之前或之后,因此工作台的实现可能如下所示:

(bounds, position) => DropType.Inside

对于 Field,您可以使用 Simple Card Sort example 中的逻辑,其中 DropTarget 的上半部分转换为 Before 放置,而下半部分转换为 After 放置:

(bounds, position) => {
const middleY = (bounds.bottom - bounds.top) / 2;
const relativeY = position.y - bounds.top;
return relativeY < middleY ? DropType.Before : DropType.After;
};

这种方法还意味着每个 DropTarget 都可以以相同的方式处理 drop() 规范方法:

  • 获取放置目标的 DOM 元素的边界
  • 获取放置位置
  • 根据边界、位置和来源计算下落类型
  • 如果发生任何掉落类型,处理掉落 Action

使用 React DnD,我们必须小心地适当处理嵌套放置目标,因为我们在 Workbench 中有 Field:

const configureDrop = getDropType => (props, monitor, component) => {
// a nested element handled the drop already
if (monitor.didDrop())
return;

// requires that the component attach the ref to a node property
const { node } = component;
if (!node) return;

const bounds = node.getBoundingClientRect();
const position = monitor.getClientOffset();
const source = monitor.getItem();

const dropType = getDropType(bounds, position, source);

if (!dropType)
return;

const { onDrop, ...target } = props;
onDrop(source, target, dropType);

// won't be used, but need to declare that the drop was handled
return { dropped: true };
};

Component 类最终看起来像这样:

@connect(...)
@DragSource(ItemTypes.FIELD, {
beginDrag: ({ unique, parent, attributes }) => ({ unique, parent, attributes })
}, dragCollect)
// IMPORTANT: DropTarget has to be applied first so we aren't receiving
// the wrapped DragSource component in the drop() component argument
@DropTarget(ItemTypes.FIELD, {
drop: configureDrop(getFieldDropType)
canDrop: ({ parent }) => parent // don't drop if it isn't on the Workbench
}, dropCollect)
class Field extends React.Component {
render() {
return (
// ref prop used to provide access to the underlying DOM node in drop()
<div ref={ref => this.node = ref}>
// field stuff
</div>
);
}

注意事项:

注意装饰顺序。 DropTarget 应该包裹组件,然后 DragSource 应该包裹包裹的组件。这样,我们就可以在 drop() 中访问正确的 component 实例。

放置目标的根节点需要是原生元素节点,而不是自定义组件节点。

任何将使用 configureDrop()DropTarget 装饰的组件将要求组件将其根节点的 DOM ref 设置为节点 属性。

因为我们在 DropTarget 中处理拖放,DragSource 只需要实现 beginDrag() 方法,它会返回您想要混合到您的应用程序状态中的任何状态。

最后要做的是处理 reducer 中的每种掉落类型。重要的是要记住,每次移动某些东西时,都需要从其当前父级(如果适用)中删除源,然后将其插入到新父级中。每个 Action 最多可以改变三个元素的状态,即源的现有父元素(以清理其 children)、源(以分配其 parent 引用)和目标的父级或目标,如果 Inside 掉落(添加给它的 child )。

您可能还想考虑将您的状态设为对象而不是数组,这样在实现 reducer 时可能更容易使用。

{
AWJOPD: { ... },
DAWPNC: { ... },
workbench: {
key: 'workbench',
parent: null,
children: [ 'DAWPNC' ]
}
}

关于javascript - 在 React DnD drop 中获取 DOM 中的元素位置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47500136/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com