React DnD:避免使用findDOMNode

时间:2016-11-09 01:56:59

标签: reactjs react-dnd

我不完全理解它,但显然it isn't recommended to use findDOMNode()

我尝试创建拖放组件,但我不确定如何从组件变量访问refs。这是我目前拥有的一个例子:

const cardTarget = {
    hover(props, monitor, component) {
        ...
        // Determine rectangle on screen
        const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
        ...
    }
}

Source

修改

这可能是由于我的组件既是拖放源又是目标,因为我可以在this example中使用它而不是this one

3 个答案:

答案 0 :(得分:20)

假设您正在使用es6类语法和最新版本的React(15,在撰写本文时),您可以在您共享的链接上附加一个回调参考,就像Dan在他的示例中所做的那样。来自the docs

  

当在HTML元素上使用ref属性时,ref回调接收底层DOM元素作为其参数。例如,此代码使用ref回调来存储对DOM节点的引用:

<h3
    className="widget"
    onMouseOver={ this.handleHover.bind( this ) }
    ref={node => this.node = node}
>

然后您就可以像我们以前与我们的老朋友findDOMNode()getDOMNode()一样访问该节点:

handleHover() {
  const rect = this.node.getBoundingClientRect(); // Your DOM node
  this.setState({ rect });
}

行动中: https://jsfiddle.net/ftub8ro6/

编辑:

因为React DND在幕后做了一些魔术,我们必须使用他们的API来获取装饰组件。它们提供getDecoratedComponentInstance(),因此您可以获得基础组件。使用后,您可以按预期获得component.node

hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;
    const rawComponent = component.getDecoratedComponentInstance();
    console.log( rawComponent.node.getBoundingClientRect() );
    ...

这是在行动:

https://jsfiddle.net/h4w4btz9/2/

答案 1 :(得分:2)

更好的解决方案

更好的解决方案是使用div包装可拖动组件,在其上定义一个引用并将其传递给可拖动组件,即

<div key={key} ref={node => { this.node = node; }}>
  <MyComponent
    node={this.node}
  />
</div>

MyComponent包含在DragSource中。现在你可以使用

hover(props, monitor, component) {
  ...
  props.node && props.node.getBoundingClientRect();
  ...
}

(仅添加props.node &&以避免在未定义的对象上调用getBoundingClientRect

findDOMNode

的替代方案

如果您不想添加换行div,则可以执行以下操作。 @imjared和the suggested solution here的回复不起作用(至少在react-dnd@2.3.0react@15.3.1中)。

findDOMNode(component).getBoundingClientRect();唯一不使用findDOMNode的替代方法是:

hover(props, monitor, component) {
  ...
  component.decoratedComponentInstance._reactInternalInstance._renderedComponent._hostNode.getBoundingClientRect();
  ...
}

这不是很漂亮且危险,因为react可能会在以后的版本中更改此内部路径!

其他(较弱)替代

使用monitor.getDifferenceFromInitialOffset();,它不会为您提供精确的值,但如果您有一个小的 dragSource ,它可能已经足够了。然后,根据 dragSource 的大小,返回的值非常容易预测,误差范围很小。

答案 2 :(得分:0)

React-DnD的API非常灵活 - 我们可以(ab)使用它。

例如,React-DnD允许我们确定将哪些连接器传递给底层组件。这意味着我们也可以包装它们。 :)

例如,让我们覆盖目标连接器以将节点存储在监视器上。我们将使用Symbol,因此我们不会将这个小黑客泄露给外界。

const NODE = Symbol('Node')

function targetCollector(connect, monitor) {
  const connectDropTarget = connect.dropTarget()
  return {
    // Consumer does not have to know what we're doing ;)
    connectDropTarget: node => {
      monitor[NODE] = node
      connectDropTarget(node)
    }
  }
}

现在,在hover方法中,您可以使用

const node = monitor[NODE]
const hoverBoundingRect = node.getBoundingClientRect()

这种方法依赖于React-DnD的流程,并使用Symbol来屏蔽外部世界。

无论您是使用此方法还是基于类的this.node = node ref方法,您都依赖于底层的React节点。我更喜欢这个,因为消费者不必记住手动使用除了React-DnD已经要求的ref之外的import tkinter as tk from tkinter import ttk from PIL import Image, ImageTk # Custom Label Class class ResizingLabel(tk.Label): #inherit from Label Class def __init__(self, parent, imagepath, *args, **kwargs): # specific for this Subclass: give me an image-path and resize image to label-size tk.Label.__init__(self, parent, *args, **kwargs) # I can do everything what tk.Label can do # Default configure settings (Label has no border) self.configure(bd=0) self.parent=parent self.parent.bind('<Configure>', self._resize_image) self.imagepath = imagepath self.image = Image.open(self.imagepath) self.img_copy= self.image.copy() self.background_image = ImageTk.PhotoImage(self.image) self.background = tk.Label(self, image=self.background_image,bd=0) self.background.grid(row=0,column=0,sticky="NSEW") self.background.grid_rowconfigure(0,weight=1) self.background.grid_columnconfigure(0,weight=1) self.background.bind('<Configure>', self._resize_image) def _resize_image(self,event): new_width = self.parent.winfo_width() new_height = self.parent.winfo_height() self.image = self.img_copy.resize((new_width, new_height)) self.background_image = ImageTk.PhotoImage(self.image) self.background.configure(image = self.background_image) # For the follwing code: See SentDex-Tutorial LARGE_FONT= ("Verdana", 12) class SeaOfBTC(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) tk.Tk.wm_title(self,"Sea of BTC") container = tk.Frame() container.pack(side="top", fill="both", expand = True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) self.frames = {} for F in (StartPage, PageOne): frame = F(container,self) self.frames[F] = frame frame.grid(row=0, column=0, sticky="nsew") self.show_frame(StartPage) def show_frame(self, cont): frame = self.frames[cont] frame.tkraise() class StartPage(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self,parent) # Create string object with path path = "banner.png" # Create custom label with reference to path label = ResizingLabel(self, path) label.grid() # Create example button on top of custom label button = tk.Button(label,text="Example Button") button.grid(column=0,row=0) class PageOne(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent) # This frame contains nothing (only exists so that for-loop (see above) can iterate) app = SeaOfBTC() app.mainloop() ,消费者也不必是类组件。