React DnD: избегать использования findDOMNode

Я не совсем понимаю это, но, по-видимому, не рекомендуется использовать findDOMNode().

Я пытаюсь создать компонент перетаскивания, но я не уверен, как мне обращаться к refs из переменной компонента. Это пример того, что у меня есть:

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

Источник

Edit

Это может быть вызвано тем, что мой компонент является как источником перетаскивания, так и целью, поскольку я могу заставить его работать в в этом примере, но не этот.

Ответ 1

Предполагая, что вы используете синтаксис класса es6 и самую последнюю версию React (15, на момент написания), вы можете прикрепить обратный вызов, как это сделал Дэн в своем примере по ссылке, которую вы поделили. Из документы:

Когда атрибут ref используется в элементе HTML, обратный вызов ref получает в качестве аргумента базовый элемент DOM. Например, этот код использует обратный вызов ref для хранения ссылки на DOM node:

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

Затем вы можете получить доступ к node так же, как мы привыкли делать с нашими старыми друзьями findDOMNode() или getDOMNode():

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

В действии: https://jsfiddle.net/ftub8ro6/

Edit:

Поскольку 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/

Ответ 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 объекта undefined)

Альтернатива для findDOMNode

Если вы не хотите добавлять обертку div, вы можете сделать следующее. Ответ @imjared и предлагаемого решения здесь не работает (по крайней мере, в [email protected] и [email protected]).

Единственной рабочей альтернативой для findDOMNode(component).getBoundingClientRect();, которая не использует findDOMNode, является:

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

который не очень красив и опасен, потому что react может изменить этот внутренний путь в будущих версиях!

Другое (более слабое) Альтернатива

Используйте monitor.getDifferenceFromInitialOffset();, который не даст вам точных значений, но, возможно, достаточно хорош, если у вас есть небольшой dragSource. Тогда возвращаемое значение довольно предсказуемо с небольшим размером ошибки в зависимости от размера вашего dragSource.

Ответ 3

React-DnD API очень гибкий - мы можем (ab) использовать это.

Например, React-DnD позволяет нам определить, какие соединители передаются базовому компоненту. Это означает, что мы тоже можем их обернуть.:)

Например, допустим переопределение целевого соединителя для хранения node на мониторе. Мы будем использовать 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 node. Я предпочитаю это, потому что потребитель не должен забывать вручную использовать ref, кроме тех, которые уже требуются React-DnD, и потребитель тоже не должен быть компонентом класса.