React-dnd: 不能同时有两个HTML5后端

创建于 2015-06-08  ·  62评论  ·  资料来源: react-dnd/react-dnd

嗨,丹,

很快-我正在尝试使用自己的具有react-dnd作为另一个应用程序的依赖项,该应用程序_itself使用了react-dnd因此出现了超出预期的错误。 在这种情况下,解决此问题的最佳方法是什么?

由于另一个组件是我自己的,因此我可以在导出该组件时从中删除DragDropContext调用,但这会牺牲该组件的可重用性。 你有什么建议?

最有用的评论

另一种更干净的方法是创建一个模块,该模块为特定的后端生成装饰器,然后在需要的地方使用装饰器:

lib / withDragDropContext.js

import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

export default DragDropContext(HTML5Backend);

组件/MyComponent.js

import { Component } from 'react';
import withDragDropContext from '../lib/withDnDContext';

class MyComponent extends Component {

  render() {
    return (
     <div>
       // other children
     </div>
   );
}

export default withDragDropContext(MyComponent);

所有62条评论

查看DragDropContext的源代码。 我认为,如果树上方的组件指定了现有管理器,则您应该能够重用它。 但这仍然是一个棘手的问题。您有任何建议的解决方案吗?

我认为,如果树上方的组件指定了现有管理器,则您应该能够重用它。

即使有可能,导出的组件也应该能够独立工作,并且在后端存在的情况下(在使用该组件的应用程序中),应该在链的某个位置重复使用。 我将尝试阅读源代码,看看是否可以将其删除。

不幸的是,我现在唯一能想到的解决方案是按原样导出最终组件,并期望用户在选择的后端添加DragDropContext 。 你怎么看?

导出的组件也应该能够独立工作,并且在后端存在的情况下(在使用该组件的应用程序中),应该重用链上的某个位置。

是的,这可以通过不完全使用DragDropContext而在上下文中手动使用dragDropManager来实现。 您的组件可能会调查上下文,并在上下文中传递现有的dragDropManager或创建自己的上下文。 不过,它似乎有些脆弱。

不幸的是,我现在唯一能想到的解决方案是按原样导出最终组件,并希望用户添加一个自己选择的后端的DragDropContext。 你怎么看?

我认为这是最灵活的解决方案。 您也可以在那里自以为是和出口<MyComponentContext>适用DragDropContext(HTML5Backend)

导出适用DragDropContext(HTML5Backend) <MyComponentContext> DragDropContext(HTML5Backend)

对不起,我对此不太了解。 您能再澄清一点吗?

export default function MyTagControlContext(DecoratedClass) {
  return DragDropContext(HTML5Backend)(DecoratedClass);
}

您可以告诉用户将顶级组件包装到MyTagControlContext或者如果他们已经使用React DnD,则直接使用DragDropContext

啊! 这个怎么样? 这看起来太丑吗?

// in main component file
module.exports = {
    WithContext: DragDropContext(HTML5Backend)(ReactTags),
    WithOutContext: ReactTags
};

用法可以像

var ReactTags = require('react-tags').WithContext; // if your app doesn't use react-dnd
var ReactTags = require('react-tags').WithOutContext; // if your app already uses react-dnd.

我认为这行不通,因为每个<ReactTags>都会获得自己的后端副本,并导致您上面粘贴的不变错误,因为这些后端处理相同的全局窗口事件。

我认为可以使用的方法是,您可以手动创建dragDropManager (就像DragDropContext在内部进行操作一样),并为所有ReactTag实例使用相同的实例,同时回退到在context定义的经理。

我的意思是从您的库中导出这样的内容:

let defaultManager;
function getDefaultManager() {
    if (!defaultManager) {
        defaultManager = new DragDropManager(HTML5Backend);
    }
    return defaultManager;
}

class ReactTagContext {
    static contextTypes = {
        dragDropManager: PropTypes.object.isRequired
    };

    static childContextTypes = {
        dragDropManager: PropTypes.object.isRequired
    };

    getChildContext() {
        return {
            dragDropManager: this.context.dragDropManager || getDefaultManager()
        };
    }

    render() {
        return <ReactTag {...props} />
    }
}

非常感谢,丹! 我会尝试的,并尽快回复您。 感谢您分享代码:咧嘴笑:

没有问题。如果您这样做,只需导出该类,而不是直接导出ReactTags 。 它应该“按原样”使用,没有任何包装或装饰器。

丹! 出于麻烦,我正在尝试上述多重出口解决方案-

// in main component file
module.exports = {
    WithContext: DragDropContext(HTML5Backend)(ReactTags),
    WithOutContext: ReactTags
};

在我的另一个应用程序中,我尝试在没有上下文的情况下导入组件令我高兴的是,它似乎运行良好!

您认为这是一个骇人听闻的解决方案,我应该继续执行您的建议,还是应该这样做?

@ prakhar1989您确定这可与页面上的多个<Tags />一起使用吗?

你在那里有我一秒钟! :stuck_out_tongue:

img

幸运的是,它有效!

嗯,那也许很好;-)。 如果您对此方法有任何疑问,请告诉我!

会做! 再次感谢您的所有帮助。

PS:您对于WithContextWithoutContext有更好的命名思路吗?

@ prakhar1989我可能只是直接导出WithContext版本,并将NoContext作为静态字段放在上面。

我遇到了类似的问题,我想更好地理解为什么此限制首先存在,因为这使编写可重用的组件变得非常困难。 实际上,每个使用react-dnd的组件都需要了解应用程序中可能存在的各种上下文,并相应地进行处理。 如果每个组件都可以管理自己的拖动行为/上下文,而不考虑应用程序其余部分中可能发生的其他事情,那将是更好的选择。

例如,我可能想要一个应用程序屏幕,其中包含几个“文件上传”组件,一个可排序的菜单以及一个具有可拖动元素的游戏。 这些组件中的每一个都有处理拖动事件的非常不同的方式,并且应该真正负责它们自己的上下文。

我的第一个问题是,为什么不简单地在HTML5Backend代码中执行此操作?

setup() {
    ...

    // Events already setup - do nothing
    if (this.constuctor.isSetUp) return;

    // Don't throw an error, just return above.
    //invariant(!this.constructor.isSetUp, 'Cannot have two HTML5 backends at the same time.');

    this.constructor.isSetUp = true;
    ...
  }

为什么不简单地在HTML5Backend代码中执行此操作

这是一个好点。 如果组件能够检测到多个后端,那么它不能直接具有在范围上回退到现有后端的逻辑吗?

@gaearon ,您好:我也DragDropContext包裹了每个包裹,这会导致此错误。 我尝试遵循上述线程,但对我来说,对于这些单独的react组件共享上下文而不将页面上的其他所有内容都不转换为React(不理想)我该怎么办。 我对ES6语法有点熟悉,但是我正在从事一个仍在使用ES5的项目。 有没有一种方法可以从上面应用共享的DragDropManager概念? 到目前为止,我已经尝试过了,但似乎无法访问DragDropManager因为它位于dnd-core

感谢您的帮助和这个很棒的图书馆!

PS如果很重要,我正在使用ngReact。

@ prakhar1989 @globexdesigns @gaearon

我在想同样的事情,如果使用多个HTML5后端,为什么HTML5后端不能仅仅重复使用该后端? 根据我之前的评论,这确实使我无法使用react-dnd,因为我在一个有角度的页面中有多个反应区域,这些区域需要能够彼此之间形成DnD,并因此碰壁。

有人对此有任何快速解决方案吗? 我的发展停滞不前。

@abobwhite就是我的解决方法。 绝对不是一个很好的解决方案,但到目前为止它似乎仍然有效。

希望这可以帮助,

谢谢@ prakhar1989 ! 但是我没有关注多重导出如何用上下文包裹一个而不能解决问题的问题。 我的问题不是潜在地用react-dnd嵌入到另一个应用程序中,而是不能在react中包装我启用了dnd的整个区域(带有几个react和angular组件/指令),所以我试图将那些内容包装在页面中对支持DnD的组件进行反应...我很想从上面尝试@gaearon的方法,但是我无权访问DragDropManager来新建一个...

我有完全一样的问题。 我非常同意@abobwhite的意见,因为这会使组件的可重用性降低。

该线程使我能够通过在组件层次结构中将HTML5BackendDragDropContext向上移动来解决我的不变式问题,就像docs所建议的那样

这是一个奇怪的问题。 我正在处理一个嵌套的可重排序组件,并且我的DragDropContext嵌套在父组件中。 它似乎可以独立工作(但它仍然嵌套了DragDropContext)。

但是,当我在层次结构上方已初始化DragDropContext的另一个项目中使用该组件时,出现此错误。

我正在使用的应用程序遇到此问题。 我对所有组件都拥有完全控制权,所以我最终没有使用@DragDropContext(HTMLBackend)此注释中最终使用了非常类似于@gaearon代码的东西来创建decorator ,以提供共享拖动删除上下文。 它真的很好。

我只是发生了同样的事情,问题是我有使用@DragDropContext(HTMLBackend) ComponentA,然后是也有@DragDropContext(HTMLBackend) ComponentB。

ComponentB是在ComponentA中导入的,这对我造成了错误。 我要做的就是从ComponentB中删除DragDropContext,并且可以正常工作。

如果应用程序中有2个DrapDropContext但他们不是父母和孩子,该怎么办?

我的情况:

  1. 初始化第一个dnd上下文
  2. 初始化为第二个dnd Context。 💣

我无法检查childContext,因为第二个组件不是第一个dnd组件/上下文的子代

为了解决我的问题,我使用以下代码进行了单例:

import { DragDropManager } from 'dnd-core';
import HTML5Backend from 'react-dnd-html5-backend';

let defaultManager;

/**
 * This is singleton used to initialize only once dnd in our app.
 * If you initialized dnd and then try to initialize another dnd
 * context the app will break.
 * Here is more info: https://github.com/gaearon/react-dnd/issues/186
 *
 * The solution is to call Dnd context from this singleton this way
 * all dnd contexts in the app are the same.
 */
export default function getDndContext() {
  if (defaultManager) return defaultManager;

  defaultManager = new DragDropManager(HTML5Backend);

  return defaultManager;
}

然后,在所有有一个拥有DragDropContext(HTML5Backend)的孩子的组件中,我将其从这些孩子中删除,然后在他们的父母中,我这样做:

import getDndContext from 'lib/dnd-global-context';

const ParentComponent = React.createClass({

  childContextTypes: {
    dragDropManager: React.PropTypes.object.isRequired,
  },

  getChildContext() {
    return {
      dragDropManager: getDndContext(),
    };
  },

  render() {
    return (<ChildComponentWithDndContext />);
  },

我认为关键是我只初始化一次dnd上下文。 你怎么看?

@andresgutgon谢谢。 它也对我有用

@andresgutgon您的解决方案很棒,但是奇怪的是DrapDropContext不会破坏DragDropManager上的componentWillUnmount ,即,如果您不使用2个DrapDropContext s同时,而是在2个不同的页面上-您仍然无法使用它们。 我将尝试以您的方式进行黑客攻击,但对于这种微不足道的情况,即使有黑客攻击仍然100%很奇怪。

还知道为什么有2个DragDropManagerreact-dnd是不好/错误吗?

另一种更干净的方法是创建一个模块,该模块为特定的后端生成装饰器,然后在需要的地方使用装饰器:

lib / withDragDropContext.js

import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

export default DragDropContext(HTML5Backend);

组件/MyComponent.js

import { Component } from 'react';
import withDragDropContext from '../lib/withDnDContext';

class MyComponent extends Component {

  render() {
    return (
     <div>
       // other children
     </div>
   );
}

export default withDragDropContext(MyComponent);

您好,当您将一个组件封装在DragDropContext中,然后将另一个组件也使用DragDropContext封装时,有人知道如何解决吗,但是此组件(大日历)是一个npm包,因此从那里将其删除没有解决方案,他们彼此相邻,而不是亲子...您可以在这里看看https://github.com/martinnov92/TSCalendar (正在开发中-有点杂乱:D)谢谢

我相信使用react-mosaic平铺窗口管理器会遇到完全相同的问题,后者使用react-dnd和我自己的组件,这些组件也都使用react-dnd。

所以我猜想这不是在react-dnd中直接解决的,但是有一些解决方法可以应用。

有完整的工作代码示例可以解决此问题吗?

@gcorne的解决方案起作用

嗨,我有同样的问题,但我们使用react-data-grid并尝试添加react-big-calendar
添加包含react-big-calendar的组件后,我们将显示错误“未捕获的错误:不能同时具有两个HTML5后端”。

react-big-calendar使用react-dnd和react-dnd-html5-backend如何解决该问题?

@szopenkrk您是否尝试过这个https://github.com/react-dnd/react-dnd/issues/186#issuecomment -232382782吗?
但我认为您需要执行拉取请求以响应big-calendar以接受Dnd后端

可能为时已晚,但我想出了一个类似但略有不同的解决方案。 我实现了一个更高阶的组件,并在所有我的DragDropContext感知组件中简单地使用了它。

代码如下所示(TypeScript):

import * as React from 'react';
import {DragDropContext} from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

// context singleton
let context: Function;

export function withDragDropContext<P>(
    Component: React.ComponentClass<P> | React.StatelessComponent<P>,
): React.ComponentClass<P> {
    // ensure a singleton instance of the context exists
    if (!context) {
        context = DragDropContext<P>(HTML5Backend);
    }

    return context(Component);
}

然后在组件中按如下所示使用它:

import * as React from 'react';
import {withDragDropContext} from 'components/WithDragDropContext';

class MyClass extends React.Component<IMyClassProps, {}> {
    // ...
}

export default withDragDropContext<IMyClassProps>(MyClass);

NB
我还没有尝试过,但是您可能应该可以在声明期间填充上下文变量:

const context = DragDropContext(HTML5Backend);

然后跳过if (!context) {...部分。

@codeaid谢谢!

我遇到了与@andresgutgon相同的情况,但是无法用他的方法解决,我在这里尝试的所有方法都没有用,没有人可以帮助我吗? 谢谢。

为什么@ guang2013这里的任何解决方案都

不,仍然没有解决方案,我不知道,我试图将卡拖动到bigCalendar中,然后将卡设置为dragSource,将cardsList设置为DragAndDropContext,将日历设置为另一个DragAndDropContext,然后抛出了两个html5backend错误,我尝试了使用此处提供的任何方法,但没人能解决我的问题。 @andresgutgon ,您何时在线,可否直接与您联系? 非常感激。

@ guang2013如果您正在使用依赖于react-dnd中的DragDropContext的库,则此技术将不起作用,因为该库不会使用您的统一dndContext。 有些库(例如react-sortable-tree)将允许您在没有上下文的情况下使用组件,因此您可以自己包装它们。

@gcorne的解决方案对我

这是旧问题,但万一有人从Google到此为止:

我的页面上只有1个DND提供程序,我没有集成任何其他具有DND的库,但最终还是碰到了这个错误,有些随机。

我的问题是DragDropContextProvider位于ReactRouter的BrowserRouter元素内,这使得HTML5Backend可以在每次导航时重建,并且如果旧页面(被导航离开的页面)和新页面(被导航到的页面)都具有DND元素,上面的错误将会发生。

解决方案是将DragDropContextProvider移出BrowserRouter。

这是针对那些首次尝试使用ReactDnD,遵循本教程并最终得到64平方米棋盘的凡人。
我可以随便拖我的骑士。
问题是,当我尝试将其放入BoardSquare ,出现了2个HTML5后端问题。

修复

正如其他人之前在评论中提到的那样,将DragDropContextProvider渲染移至应用程序重新渲染周期之外。
与之类似,请勿直接执行ReactDOM.render作为observe函数的回调。
而是这样做:

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { DragDropContextProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import './index.css';
import App from './App';

ReactDOM.render(
    <DragDropContextProvider backend={HTML5Backend}>
        <App />
    </DragDropContextProvider>,
    document.getElementById('root')
)

App.js

import React, { Component } from 'react';
import Board from './Board';
import { observe } from './Game';

class App extends Component {
    state = {
        knightPosition: [0, 0]
    }

    componentDidMount = () => {
        observe(knightPosition => {
            this.setState(prevState => ({
                ...prevState,
                knightPosition
            }));
        });
    }

    render() {
        return (
            <div className="App">
                <Board knightPosition={this.state.knightPosition} />
            </div>
        );
    }
}

export default App;

我认为这与这个问题有关
https://github.com/prakhar1989/react-tags/issues/497

@gaearon我知道这很旧了,但是在您的示例中,如何实际获得DragDropManager的保留

defaultManager = new DragDropManager(HTML5Backend);

我的问题是我正在通过某些第三方API在页面上呈现一些小部件,而我无法将DragDropContextProvider移到比实际小部件高的位置。

通过删除旧版本解决了此问题。
只需删除dist文件夹或编辑index.html。 我不知道确切的问题,但这对我有用

幸运的是,我可以找到@gcorne的(隐藏)答案(https://github.com/react-dnd/react-dnd/issues/186#issuecomment-282789420)。 它立即解决了我的问题-这被认为是棘手的。
@ prakhar1989我觉得这是一个真实的聚会答案,应该以某种方式突出显示它,因此可以将其链接到错误描述中吗?

我发现最适合我的解决方案是,允许我使用HTML5或Touch后端:

创建一个单例HOC组件:

import {DragDropContext} from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import TouchBackend from "react-dnd-touch-backend";

const DragAndDropHOC = props => {
    return <React.Fragment>
        {props.children}
    </React.Fragment>
};

export default {
    HTML5: DragDropContext(HTML5Backend)(DragAndDropHOC),
    Touch: DragDropContext(TouchBackend)(DragAndDropHOC),
}

然后是提供程序组件:

const DragDrop = props => {
    if (props.isTouch) {
        return <DragDropContext.Touch>{props.children}</DragDropContext.Touch>
    } else {
        return <DragDropContext.HTML5>{props.children}</DragDropContext.HTML5>
    }
};

并在需要的地方使用<DragDrop isTouch={props.isTouch} />

对于遇到相同问题的开发人员,您可以看一下此HOC解决方案

我现在用Jest测试遇到了这个问题。 HTML5后端在Jest测试中被视为单例(这就是为什么问题在发生……我认为)

SO中的详细问题:

https://stackoverflow.com/questions/58077693/multiple-react-dnd-jest-tests-cannot-have-two-html5-backends-at-the-same-time

使用挂钩

import { createDndContext } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";

const manager = useRef(createDndContext(HTML5Backend));

return (
  <DndProvider manager={manager.current.dragDropManager}>
      ....
  </DndProvider>
)

使用Hooks的更好解决方案(感谢@jchonde):

import { DndProvider, createDndContext } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import React, { useRef } from "react";

const RNDContext = createDndContext(HTML5Backend);

function useDNDProviderElement(props) {
  const manager = useRef(RNDContext);

  if (!props.children) return null;

  return <DndProvider manager={manager.current.dragDropManager}>{props.children}</DndProvider>;
}

export default function DragAndDrop(props) {
  const DNDElement = useDNDProviderElement(props);
  return <React.Fragment>{DNDElement}</React.Fragment>;
}

因此,您可以在其他地方使用:

import DragAndDrop from "../some/path/DragAndDrop";

export default function MyComp(props){
   return <DragAndDrop>....<DragAndDrop/>
}

我的解决方案:
使子组件不直接导入react-dnd
DragDropContextHTML5Backend从父级传递给子级组件。

DragDropContextProvider标签里加一个不重复的key就解决了<DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

在打字稿中,我做了以下内容。 (感谢@jchonde @ttessarolo

import { DndProvider, createDndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import React, { PropsWithChildren, useRef } from 'react';

const RNDContext = createDndContext(HTML5Backend);

function DragAndDrop({ children }: PropsWithChildren<{}>): JSX.Element {
  const manager = useRef(RNDContext);
  return <DndProvider manager={manager.current.dragDropManager}>{children}</DndProvider>;
}

export default DragAndDrop;

并使用了这样的组件

function SomeComponent(): JSX.Element {
  return (
    <DragAndDrop>
      ...
    </DragAndDrop>
  );
}

DragDropContextProvider标签里加一个不重复的key就解决了<DragDropContextProvider backend={HTML5Backend} key={Math. random()}></DragDropContextProvider>

good job!

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