React-dnd: 「canDrop」、どの要素の上にあるかを知る方法は?

作成日 2016年04月14日  ·  4コメント  ·  ソース: react-dnd/react-dnd

drop(props, monitor, component)メソッドには、3つの引数があります。 しかし、 canDrop(props, monitor)には、 component引数はありません。

アイテムを別のコンポーネントの上にドラッグしたときに、そのコンポーネントを50%(高さ)の2つの部分に「分割」したいと思います。 アイテムが上部にドロップされた場合はその上に配置され、下部にドロップされた場合は後に配置されます。

コンポーネントが上部/下部にドロップされているかどうかを検出するコードはすでにあります。 ここで必要なのは、マウスが上部/下部にあるかどうかに基づいてクラスを追加することです(ただし、ドロップアクションではなく、ホバーアクション中に)。

hover(props, monitor, component)メソッドを使用できるかどうかわかりません。これは、コンポーネントにプロパティを追加する方法がわからないためです(DOMなどにCSSクラスを追加します)。これは、コンポーネント自体に属していないため、コンテキストなどにアクセスできないためです。

何かが足りないと思います。 :)

最も参考になるコメント

さて、コンポーネントに状態を設定し、状態が設定されたらクラスを追加するという考え方です。

Fooをバーにドラッグしているとしましょう。

import React, { Component } from "react";
import classNames from "classnames";

class Bar extends Component {
  constructor(props) {
    super(props);

    this.state = {
      // Where a foo is hovering over the component.  One of null, "above", or "below"
      fooHover: null,
    };
  }

  render() {
    const { fooHover } = this.state;

    const classes = classNames("bar", {
      "bar-above": fooHover === "above",
      "bar-below": fooHover === "below",
    });
  }
}

唯一の問題は、その状態をどのように設定するかです。

import { findDOMNode } from "react-dom";

const barTarget = {
  ...
  hover(props, monitor, component) {
    if (!monitor.canDrop()) {
      return;
    }

    const rawComponent = undecorate(component); // undecorate described below

    const { y } = monitor.getClientOffset();
    const { top, height } = findDOMNode(component).getBoundingClientRect();

    if (y < top + height/2) {
      rawComponent.setFooHover("above"); // setFooHover described below
    } else {
      rawComponent.setFooHover("below");
    }
  },
  ...
}

したがって、基本的には、コンポーネントのインスタンスメソッドsetFooHoverを呼び出します。これにより、 setStateが呼び出されます。 パフォーマンス上の理由から、次のようになります。

  // In Bar
  setFooHover(fooHover) {
    if (fooHover !== this.state.fooHover) {
      this.setState({ fooHover });
    }
  }

上記でundecorateと呼んでいる理由は、react-dndがより高次のコンポーネントを使用しており、ラップされていないコンポーネントが必要だからです。 私の意見では、これは高次コンポーネントの醜い複雑さです。 代わりに、フラックスストアなどにアクションをディスパッチすることでこれを実装できますが、私のように実行することにした場合は、ここに私のundecorateます。

function undecorate(component) {
  let curr = component;
  while (typeof curr.getDecoratedComponentInstance === "function") {
    curr = curr.getDecoratedComponentInstance();
  }

  return curr;
}

これを実装すると、1つの小さな問題を除いて、すべてが良好に見えます。 クラスは決して消えません! setHoverState(null)呼び出す人は誰もいません(barTargetにendHover関数などはありません)。 これを処理する適切な方法は、次のようにcomponentWillReceivePropsを使用することです。

  componentWillReceiveProps(nextProps) {
    if (this.props.isFooOver && !nextProps.isFooOver) {
      this.setState({ fooHover: null });
    }
  }

(収集関数に{ isFooOver: monitor.isOver() }を追加することを忘れないでください)

どうなるか教えてください!

全てのコメント4件

ねえ、あなたは運がいいです! 私は以前にこれをほぼ正確に実装したことがあるので、いくつかのコードをまとめましょう。

さて、コンポーネントに状態を設定し、状態が設定されたらクラスを追加するという考え方です。

Fooをバーにドラッグしているとしましょう。

import React, { Component } from "react";
import classNames from "classnames";

class Bar extends Component {
  constructor(props) {
    super(props);

    this.state = {
      // Where a foo is hovering over the component.  One of null, "above", or "below"
      fooHover: null,
    };
  }

  render() {
    const { fooHover } = this.state;

    const classes = classNames("bar", {
      "bar-above": fooHover === "above",
      "bar-below": fooHover === "below",
    });
  }
}

唯一の問題は、その状態をどのように設定するかです。

import { findDOMNode } from "react-dom";

const barTarget = {
  ...
  hover(props, monitor, component) {
    if (!monitor.canDrop()) {
      return;
    }

    const rawComponent = undecorate(component); // undecorate described below

    const { y } = monitor.getClientOffset();
    const { top, height } = findDOMNode(component).getBoundingClientRect();

    if (y < top + height/2) {
      rawComponent.setFooHover("above"); // setFooHover described below
    } else {
      rawComponent.setFooHover("below");
    }
  },
  ...
}

したがって、基本的には、コンポーネントのインスタンスメソッドsetFooHoverを呼び出します。これにより、 setStateが呼び出されます。 パフォーマンス上の理由から、次のようになります。

  // In Bar
  setFooHover(fooHover) {
    if (fooHover !== this.state.fooHover) {
      this.setState({ fooHover });
    }
  }

上記でundecorateと呼んでいる理由は、react-dndがより高次のコンポーネントを使用しており、ラップされていないコンポーネントが必要だからです。 私の意見では、これは高次コンポーネントの醜い複雑さです。 代わりに、フラックスストアなどにアクションをディスパッチすることでこれを実装できますが、私のように実行することにした場合は、ここに私のundecorateます。

function undecorate(component) {
  let curr = component;
  while (typeof curr.getDecoratedComponentInstance === "function") {
    curr = curr.getDecoratedComponentInstance();
  }

  return curr;
}

これを実装すると、1つの小さな問題を除いて、すべてが良好に見えます。 クラスは決して消えません! setHoverState(null)呼び出す人は誰もいません(barTargetにendHover関数などはありません)。 これを処理する適切な方法は、次のようにcomponentWillReceivePropsを使用することです。

  componentWillReceiveProps(nextProps) {
    if (this.props.isFooOver && !nextProps.isFooOver) {
      this.setState({ fooHover: null });
    }
  }

(収集関数に{ isFooOver: monitor.isOver() }を追加することを忘れないでください)

どうなるか教えてください!

詳細な説明ありがとうございます! 知らせます :)

最後に、Reduxアプローチを使用したので、基本的に、ドロップイベントが発生したときに、レデューサーを介してイベントをディスパッチしました。

    drop: (propsTargetItem, monitor, targetItem) ->
        draggedItem = monitor.getItem()# Item that has been dragged and eventually dropped.
        coordsDrop = monitor.getClientOffset()# Coords of the drop action.
        diff = monitor.getDifferenceFromInitialOffset()# Comparison of the initial click (dragging start) against the end of the click (dropping start).

        patternId = draggedItem.patternId
        patternPosition = draggedItem.position
        targetId = propsTargetItem.context.patternId
        targetPosition = propsTargetItem.context.position
        isCursorAboveHalf = GeoService.isAboveHalf(ReactDOM.findDOMNode(targetItem), coordsDrop.y)

        # Extract positions in the array of children.
        oldPosition = draggedItem.position# Current position of the item, which will be the old position as soon as the item is moved. (by the server through sockets)
        newPosition = calcDropPosition(targetId, patternId, patternPosition, targetPosition, isCursorAboveHalf)# Position where we will move the dragged item.

        # Dispatch only if the position has changed and is set.
        if oldPosition isnt newPosition and newPosition?
            draggedItem.moveDnDPattern(Object.assign({}, draggedItem, {newPosition: newPosition, oldPosition: oldPosition}))

このページは役に立ちましたか?
0 / 5 - 0 評価