React-dnd: Hooks API讨论

创建于 2018-10-26  ·  43评论  ·  资料来源: react-dnd/react-dnd

最终有人要问! 新的hooks API在这里可能会有所帮助。 我认为大多数API几乎可以保持不变,因为HOC映射直接插入了钩子。

我确实想知道是否可以仅传递useRef的值来替换connectDragSourceconnectDropTarget useRef 。 如果可能的话,它绝对可以使东西更干净!

design decisions discussion

所有43条评论

我等不及要开始在这个库中使用钩子了。 一旦为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)
}

疯狂的主意,如果我们不理会connectmonitor怎么办?

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的形状要比我最初想象的要好。 在这里或那里随意发表评论

此页面是否有帮助?
0 / 5 - 0 等级