最终有人要问! 新的hooks API在这里可能会有所帮助。 我认为大多数API几乎可以保持不变,因为HOC映射直接插入了钩子。
我确实想知道是否可以仅传递useRef
的值来替换connectDragSource
和connectDropTarget
useRef
。 如果可能的话,它绝对可以使东西更干净!
我等不及要开始在这个库中使用钩子了。 一旦为Alpha烘焙了类型,我们就可以建立一个迁移分支
我在一个分支中进行实验:实验/挂钩,只是想想一下这个API的外观。 BoardSquare组件如下所示:
const dropTarget = createDropTarget(ItemTypes.KNIGHT, {
canDrop: (props: BoardSquareProps) => canMoveKnight(props.x, props.y),
drop: (props: BoardSquareProps) => moveKnight(props.x, props.y),
})
const BoardSquare = ({ x, y, children }) => {
const black = (x + y) % 2 === 1
const [connectDropTarget, isOver, canDrop] = useDnd(
dropTarget,
connect => connect.dropTarget,
(connect, monitor) => !!monitor.isOver,
(connect, monitor) => !!monitor.canDrop,
)
return connectDropTarget(
<div>
<Square black={black}>{children}</Square>
{isOver && !canDrop && <Overlay color={'red'} />}
{!isOver && canDrop && <Overlay color={'yellow'} />}
{isOver && canDrop && <Overlay color={'green'} />}
</div>,
)
}
因此,这里的想法是createDropTarget
建立有关拖放项目,其ID和谓词的逻辑知识,并且useDnd挂钩会将其连接到DnD系统并收集道具。
请注意,这只是草拟候选设计,实际上并未实现
@darthtrevino您在connectDropTarget
。 我很想看看我能否在您的分支机构上使它正常工作!
const dropTarget = createDropTarget(ItemTypes.KNIGHT, {
canDrop: (props: BoardSquareProps) => canMoveKnight(props.x, props.y),
drop: (props: BoardSquareProps) => moveKnight(props.x, props.y),
})
const BoardSquare = ({ x, y, children }) => {
const dropTargetElement = useRef(null);
const black = (x + y) % 2 === 1
const [isOver, canDrop] = useDnd(
dropTargetElement,
dropTarget,
...
)
return (
<div ref={dropTargetElement}>
<Square black={black}>{children}</Square>
{isOver && !canDrop && <Overlay color={'red'} />}
{!isOver && canDrop && <Overlay color={'yellow'} />}
{isOver && canDrop && <Overlay color={'green'} />}
</div>
)
}
@ jacobp100我认为我似乎已经使用了类似的基于钩子的API,其中ref是由钩子本身提供并返回的(例如const [isOver, canDrop, ref] = useDnd(...)
),准备将消耗组件放置在其JSX树中)
我想那行得通。 这使得在多个钩子中使用ref变得更加困难,但是没有什么可以阻止您编写将多个ref合并为一个ref的东西。 这是什么图书馆?
猜猜我们要看看这是什么约定!
这使得在多个挂钩中使用ref变得更加困难,但是没有什么可以阻止您编写将多个ref合并为一个ref的东西
正确,正确:-)
这是什么图书馆?
似乎无法在atm再次找到它:-/-在过去的两周中,围绕钩子进行了大量实验...
有一个https://github.com/beizhedenglong/react-hooks-lib
const { hovered, bind } = useHover(); return <div {...bind}>{hovered ? 'yes' : 'no'}</div>;
我猜这意味着bind
包含一个引用?
[edit:不,它只包含{ onMouseEnter, onMouseLeave }
,当然...]
但是我确实记得看到其他一些API直接从钩子中返回引用。
那里没有很多,目前还没有建立,但是我所在的分支是experiment/hooks
只是在这里迭代:
const dropTarget = createDropTarget(ItemTypes.KNIGHT, {
canDrop: props => canMoveKnight(props.x, props.y),
drop: props => moveKnight(props.x, props.y),
})
const BoardSquare = ({ x, y, children }) => {
const black = (x + y) % 2 === 1
const ref = useRef(null)
const [isOver, canDrop] = useDnd(
connect => connect(ref, dropTarget),
monitor => monitor.isOver,
monitor => monitor.canDrop,
)
return (
<div ref={ref}>
<Square black={black}>{children}</Square>
{isOver && !canDrop && <Overlay color={'red'} />}
{!isOver && canDrop && <Overlay color={'yellow'} />}
{isOver && canDrop && <Overlay color={'green'} />}
</div>,
)
}
这就是链接dragSource和dropTarget的工作方式,如果我们使用ref作为第一个参数,则其余参数可以将其连接到多个dnd概念。
const dropTarget = createDropTarget(ItemTypes.CARD, {
canDrop: () => false
hover(props, monitor) {
/**/
},
})
const dragSource = createDragSource(ItemTypes.CARD, {
beginDrag: props => /*...*/,
endDrag: (props, monitor) => /*...*/
})
function Card({ text }) {
const ref = useRef(null)
const [isDragging] = useDnd(
connect => connect(ref, dropTarget, dragSource),
monitor => monitor.isDragging,
)
const opacity = isDragging ? 0 : 1
return (
<div ref={ref} style={{ ...style, opacity }}>
{text}
</div>
)
}
所以useDnd看起来像
export type DndConcept = DragSource | DropTarget
export type ConnectorFunction = (connector: Connector /*new type*/) => void
export type CollectorFunction<T> = (monitor: DragDropMonitor) => T
export function useDnd(
connector: ConnectorFunction,
...collectors: CollectorFunction[]
): any[] {
const dragDropManager = useDragDropManager()
// magic
return collectedProperties
}
export function useDragDropManager(): DragDropManager {
return useContext(DragDropContextType)
}
疯狂的主意,如果我们不理会connect
和monitor
怎么办?
const Test = props => {
const ref = useRef(null);
const { isDragging } = useDragSource(ref, ItemTypes.CARD, {
canDrag: props.enabled,
beginDrag: () => ({ id: props.id }),
});
return <div ref={ref} style={{ color: isDragging ? 'red' : 'black' }} />
}
我认为您可以摆脱连接,但是我不确定显示器,在这里您可以获取诸如isDragging的道具
所以也许useDragSource(ref, <type>, <spec>, <collect>)
。 这有很多争论,并且两个脂肪对象彼此相邻可能很奇怪
我们可以从监视器中退还所有道具吗?
也许,我认为这是唯一会引起麻烦的方法: https :
IIRC,DragSourceMonitor,DropTragetMonitor和DragLayerMonitor都简化为DragDropMonitor类。 因此,我认为我们不会遇到命名冲突,但我会仔细检查。
@yched只是在玩这个和钩子,发现我们确实必须传递ref。请参见当某物既是源又是目标时的情况,
const Test = () => {
const ref = useRef(null)
const source = useDragSource(ref, …props)
const target = useDragTarget(ref, …props)
return <div ref={ref}>…</div>
}
@ jacobp100确实有意义。
好吧,主意,
const Test = (props) => {
const ref = useRef(null)
const sourceMonitor = useDragSource(ref, 'item' {
beginDrag: () => ({ id: props.id })
})
const targetMonitor = useDropTarget(ref, ['item'] {
drop: () => alert(targetMonitor.getItem().id)
})
const { isDragging } = useMonitorSubscription(targetMonitor, () => ({
isDragging: targetMonitor.isDragging()
})
return <div ref={ref}>…</div>
}
请注意,拖动源和目标的规范未接收任何参数,因为您已经可以访问它们
useMonitorSubscription
可以对对象执行浅相等以减少更新
我已经初步了解了这里。 没有测试,但是国际象棋示例可与钩子一起使用-应该显示我想做的事情!
我认为建议使用useDragSource(ref, <type>, <spec>, <collect>)
确实很不错,并且不会带来太多的api更改。 唯一的区别是您从临时更改为挂钩。
在我看来,将两个胖对象彼此相邻也不是一个大问题,因为您也必须这样做:
const DragDrop = () => {
const ref = useRef(null);
const dragSource = {
beginDrag() {
return props;
}
};
const collectSource = monitor => {
return {
isDragging: monitor.isDragging()
};
};
const { isDragging } = useDragSource(ref, "Item", dragSource, collectSource);
const dropTarget = {
drop() {
return undefined;
}
};
const collectTarget = monitor => {
return {
isOver: monitor.isOver()
};
};
const { isOver } = useDropTarget(ref, "Item", dropTarget, collectTarget);
return <div ref={ref}>Drag me</div>;
};
令人高兴的是,您也可以使用其他挂钩中的值。
const Drag = () => {
const ref = useRef(null);
const context = useContext(Context)
const dragSource = {
beginDrag() {
context.setDragItem(props)
return props;
},
endDrag() {
context.setDragItem(null)
}
};
const collectSource = monitor => {
return {
isDragging: monitor.isDragging()
};
};
const { isDragging } = useDragSource(ref, "Item", dragSource, collectSource);
return <div ref={ref}>Drag me</div>;
};
一个不错的好处是,我们可以从beginDrag参数中删除props和component,而其他所有函数都可以接受,因为您已经可以在范围内访问它们了。
^我刚刚更新了我的最后一条评论,以表明collectSource
不会在监视器中传递给函数-您只是从范围中读取
@ jacobp100我明白了。 对我来说,问题是我们是否需要另一个挂钩来从监视器收集数据,或者是否也可以在useDragSource和useDropTarget中实现它。
当东西是HOC时,这是很有意义的,无论如何,您必须在其中链接连接东西。
但是现在不再需要将它们耦合在一起的技术要求,因此我将它们分开。
这样,您就可以拥有更多的自由-也许一个或多个子组件可以响应监视器的更改,但不包括开始拖动的组件。 它还具有一个额外的好处,即如果您不使用监视器订阅,则该挂钩可能会摇晃。
就是说,这是初稿! 如果达成共识,我不反对更改它!
这是一个很好的论点。 我看到的一个唯一的问题是,当用户直接调用监视器并想知道为什么它无法正常运行时,用户可能会感到困惑:
const Test = (props) => {
const ref = useRef(null)
const sourceMonitor = useDragSource(ref, 'item', {
beginDrag: () => ({ id: props.id })
})
const targetMonitor = useDropTarget(ref, ['item'], {
drop: () => alert(targetMonitor.getItem().id)
})
const { isDragging } = useMonitor(targetMonitor, () => ({
isDragging: targetMonitor.isDragging()
})
return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}
在useMonitor
钩子之外调用函数时,可能可以通过文档和警告来解决。
实际上,这将起作用! 这不是我100%渴望的事情之一: useMonitor
的回调既用于更改检测,又用于返回值。 感觉好像与React内核中的当前钩子背道而驰。
也许这样的效果更好,
const Test = (props) => {
...
useMonitorUpdates(targetMonitor, () => [targetMonitor.isDragging()]);
return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}
诚然,通过这种形式引入错误要容易得多
我不是100%肯定react-dnd的内部构造,但是显示器不在那儿,因此我们不必每次鼠标移动时都渲染组件吗?
因此,如果您删除useMonitorSubscription并且在渲染函数中仅包含monitor.isDragging()
,那么前一个将停止工作?
因此,这将无法正常工作吗?
const Test = (props) => {
const ref = useRef(null)
const sourceMonitor = useDragSource(ref, 'item', {
beginDrag: () => ({ id: props.id })
})
return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}
监视器具有subscribe
方法,该方法会在值更新时通知其侦听器。 因此,我们需要执行_something_以便组件知道何时更新。
不过,在上一篇文章的基础上进行扩展,如果我们将优化变更检测作为可选功能,则可以简单地做到这一点,
const Test = (props) => {
...
useMonitorUpdates(sourceMonitor);
return <div ref={ref}>{sourceMonitor.isDragging() ? 'Dragging' : 'Drag me'}</div>
}
几个想法。
首先,我们可以通过使钩子返回Ref
实现来使ref
参数成为可选参数吗?
const dragSource = useDragSource('item', spec);
return <div ref={dragSource}/>
// or if you want to use a ref
const ref = useRef();
const dragSource = useDragSource('item', dragSourceSpec)(ref);
const dropTarget = useDropTarget('item', dropTargetSpec)(ref);
其次,不是让它们调用useMonitorUpdates
另一个钩子,我想知道我们是否可以执行以下操作:
const dragSource = useDragSource('item', spec);
const { isDragging } = dragSource.subscribe(() => ({
isDragging: targetMonitor.isDragging()
}));
由于有一个候选API,我现在要关闭它。 请随意发表新评论。 谢谢!
似乎hooks API在设计中存在缺陷: https :
有趣的是,所以我想有一种替代方法是,我们使用connect函数,就像我们当前在基于类的API中那样:
const Box = ({ name }) => {
const [{ isDragging }, dragSource] = useDrag({
item: { name, type: ItemTypes.BOX },
end: (dropResult?: { name: string }) => {
if (dropResult) {
alert(`You dropped ${item.name} into ${dropResult.name}!`)
}
},
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
})
const opacity = isDragging ? 0.4 : 1
return (
<div ref={node => dragSource(node)} style={{ ...style, opacity }}>
{name}
</div>
)
}
因此,一般而言,API看起来像...
const [props, connectDragSource, connectDragPreview] = useDrag(spec)
const [props, connectDropTarget] = useDrop(spec)
我有点希望不再需要连接器功能,但是如果没有它们就破坏了API,那么我们可以
虽然我读过这个问题-我们的API相似,但是我认为问题在于它们正在使用布局效果来获取DOM节点的度量。 我们这里并没有真正使用布局效果,只是在dnd-core中注册DOM节点。
@gaearon ,我们建议的钩子API与useDimensions API非常相似-是特定形式的反模式(例如let [props, ref] = useCustomHook(config)
),还是库试图解决的问题的特质?
@darthtrevino据我了解,如果您使用useDragSource挂钩,并将ref传递给子组件,然后子组件重新渲染,我们将不会更新dnd-core中的已注册dom节点:
function Parent() {
const ref = useRef();
const dragSource = useDragSource(ref, ...);
return <Child connect={ref} />;
}
function Child({ connect }) {
const [open, setOpen] = useState(false);
function toggle() {
setOpen(!open);
}
return (
<Fragment>
<button onClick={toggle}>Toggle</button>
{open ? <div ref={connect} /> : null}
</Fragment>
);
}
是的我将在本周晚些时候查看是否可以为此做一个测试案例,以证明它是否爆炸
如果炸毁,则使用连接器功能将成为后备
如果我正确地完成了所有操作,则可以在此处复制它: https :
Kaboom,辛苦了,谢谢@ k15a 。 我将更新钩子API以尽快使用连接功能,并将您的示例作为测试用例
因此,昨晚我花了很多时间重新设计钩子API,以使用连接器函数。 就API设计而言,我真的很讨厌它。
我的下一个想法是,我们可以返回一个callback-ref而不是ref-object。 这应该给我们灵活性,可以直接用作参考或将参考传递给它
直接使用:
let [props, dragSource] = useDrag({spec}) // dragSource result is a function
return <div ref={dragSource} {...props}>hey</div>
链式
let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})
return <div ref={node => dragSource(dropTarget(node))}>hey</div>
带参考对象
let ref = useRef(null)
let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})
dragSource(dropTarget(ref))
return <div ref={ref}>hey</div>
在我看来,如果组件或其任何子代更新,则应该触发useLayoutEffect
。 如果确实如此,我们可以使用它。
我已经在react仓库中创建了一张票。 随时发表评论。
let [dragProps, dragSource] = useDrag({spec})
let [dropProps, dropTarget] = useDrag({spec})
return <div ref={node => dragSource(dropTarget(node))}>hey</div>
我不知道它的效果如何,因为您会在每次渲染时都调用ref。 因此,对于每个渲染,都需要连接和断开连接。
这样也不会更好
node => {
dragSource(node)
dropTarget(node)
}
这是同一回事
因此,为了缓解我之前的评论,#1280中的API的形状要比我最初想象的要好。 在这里或那里随意发表评论