近期工作中使用到了 react-dnd 开发拖拽功能,开发过程中发现页面中原来使用的一个第三方组件的拖拽功能不能正常工作了。

分析

将组件单独引入页面发现工作正常,排除了第三方组件版本的原因。后面发现是 react-dnd 引入后出现的问题,猜测是 react-dnd 引入影响了原有拖拽。

解决方案

在 github 上查找到相关的 issues,有不少人遇到了同样的问题,最终在 issues/3344 中找到了答案。

具体demo可以查看 codesandbox

解决方案主要是在react-dnd初始化时传入了rootElement

import React, { useState, useCallback, useMemo } from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";

export const useDndProvider = () => {
  const [dndArea, setDndArea] = useState();
  const handleRef = useCallback((node) => setDndArea(node), []);
  const html5Options = useMemo(
    () => ({ rootElement: dndArea }),
    [dndArea]
  );
  return { dndArea, handleRef, html5Options };
};

const DndProviderWrapper = (props) => {
  const { dndArea, handleRef, html5Options } = useDndProvider();
  return (
    <div ref={handleRef}>
      {dndArea && (
        <DndProvider backend={HTML5Backend} options={html5Options}>
          {props.children}
        </DndProvider>
      )}
    </div>
  );
};

export default DndProviderWrapper;

查看官方文档发现没有关于 rootElement 的描述,看看 react-dnd-html5-backend 源码是否能找出 rootElement 的作用。

import type { BackendFactory, DragDropManager } from 'dnd-core'

import { HTML5BackendImpl } from './HTML5BackendImpl.js'
import type { HTML5BackendContext, HTML5BackendOptions } from './types.js'
export { getEmptyImage } from './getEmptyImage.js'
export * as NativeTypes from './NativeTypes.js'
export type { HTML5BackendContext, HTML5BackendOptions } from './types.js'

export const HTML5Backend: BackendFactory = function createBackend(
    manager: DragDropManager,
    context?: HTML5BackendContext,
    options?: HTML5BackendOptions,
): HTML5BackendImpl {
    return new HTML5BackendImpl(manager, context, options)
}

我们看看 HTML5BackendOptions 的定义

export type HTML5BackendContext = Window | undefined

/**
 * Configuration options for the HTML5Backend
 */
export interface HTML5BackendOptions {
    /**
     * The root DOM node to use for subscribing to events. Default=Window
     */
    rootElement: Node
}

其实这里我们已经可以看到 rootElement 的作用了,是用于订阅事件的根 DOM 节点,默认是 Window 对象。再看看具体是怎么使用的。

export class HTML5BackendImpl implements Backend {
    public constructor(
        manager: DragDropManager,
        globalContext?: HTML5BackendContext,
        options?: HTML5BackendOptions,
    ) {
        this.options = new OptionsReader(globalContext, options)
        this.actions = manager.getActions()
        this.monitor = manager.getMonitor()
        this.registry = manager.getRegistry()
        this.enterLeaveCounter = new EnterLeaveCounter(this.isNodeInDocument)
    }
    //...
    public setup(): void {
        const root = this.rootElement as RootNode | undefined
        if (root === undefined) {
            return
        }

        if (root.__isReactDndBackendSetUp) {
            throw new Error('Cannot have two HTML5 backends at the same time.')
        }
        root.__isReactDndBackendSetUp = true
        this.addEventListeners(root)
    }
        
    private addEventListeners(target: Node) {
        // SSR Fix (https://github.com/react-dnd/react-dnd/pull/813
        if (!target.addEventListener) {
            return
        }
        target.addEventListener(
            'dragstart',
            this.handleTopDragStart as EventListener,
        )
        target.addEventListener('dragstart', this.handleTopDragStartCapture, true)
        target.addEventListener('dragend', this.handleTopDragEndCapture, true)
        target.addEventListener(
            'dragenter',
            this.handleTopDragEnter as EventListener,
        )
        target.addEventListener(
            'dragenter',
            this.handleTopDragEnterCapture as EventListener,
            true,
        )
        target.addEventListener(
            'dragleave',
            this.handleTopDragLeaveCapture as EventListener,
            true,
        )
        target.addEventListener('dragover', this.handleTopDragOver as EventListener)
        target.addEventListener(
            'dragover',
            this.handleTopDragOverCapture as EventListener,
            true,
        )
        target.addEventListener('drop', this.handleTopDrop as EventListener)
        target.addEventListener(
            'drop',
            this.handleTopDropCapture as EventListener,
            true,
        )
    }
}

总结

rootElement作为根节点元素监听事件,没有传值则默认是Window对象。这个属性的说明还是比较重要的,但官方文档没有说明,还是忍不住吐槽一下-。-

原文地址:http://www.cnblogs.com/alsy/p/16817490.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性