React-dnd: "canDrop", como saber em que elemento estamos em cima?

Criado em 14 abr. 2016  ·  4Comentários  ·  Fonte: react-dnd/react-dnd

No método drop(props, monitor, component) , temos três argumentos. Mas no canDrop(props, monitor) não existe o component arg.

Quero que, quando um item for arrastado sobre outro componente, eu "divida" esse componente em duas partes de 50% (altura). Se o item for solto na parte superior, ele será colocado acima dele, se cair na parte inferior, será colocado depois.

Já tenho o código que detecta se o componente caiu na parte superior / inferior. O que eu quero agora é adicionar uma classe com base em se o mouse está na parte superior / inferior (mas durante a ação de passar o mouse, não a ação de soltar).

Não sei se posso usar o método hover(props, monitor, component) , porque não vejo como devo adicionar uma propriedade em meu componente (o que adicionaria uma classe CSS no DOM ou semelhante) de ele, uma vez que não pertence ao próprio componente e não tem acesso ao seu contexto e assim por diante.

Acho que estou perdendo alguma coisa. :)

Comentários muito úteis

OK, então a ideia é que você deseja definir o estado em seu componente e, quando o estado for definido, você adiciona a classe.

Digamos que você esteja arrastando um Foo para uma barra:

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",
    });
  }
}

A única questão é como definimos esse estado!

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");
    }
  },
  ...
}

Então, basicamente, você deseja chamar um método de instância de seu componente, setFooHover , que chamará setState . Deve ser assim por motivos de desempenho:

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

A razão pela qual estou chamando undecorate acima é que o react-dnd usa componentes de ordem superior e queremos os componentes desembrulhados. Esta é uma complicação feia de componentes de ordem superior na minha opinião. Talvez você pudesse implementar isso enviando uma ação para o seu armazenamento de fluxo ou algo assim, mas no caso de você decidir fazer como eu, aqui está o meu undecorate :

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

  return curr;
}

Agora, se você implementar isso, tudo parecerá bem, exceto por um pequeno problema. A aula nunca vai embora! Ninguém chama setHoverState(null) (não existe uma função endHover em seu barTarget). A maneira adequada de lidar com isso é usar componentWillReceiveProps assim:

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

(Não se esqueça de adicionar { isFooOver: monitor.isOver() } em sua função de coleta)

Me diga como foi!

Todos 4 comentários

Ei, você está com sorte! Eu implementei quase exatamente isso antes, então deixe-me juntar alguns códigos.

OK, então a ideia é que você deseja definir o estado em seu componente e, quando o estado for definido, você adiciona a classe.

Digamos que você esteja arrastando um Foo para uma barra:

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",
    });
  }
}

A única questão é como definimos esse estado!

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");
    }
  },
  ...
}

Então, basicamente, você deseja chamar um método de instância de seu componente, setFooHover , que chamará setState . Deve ser assim por motivos de desempenho:

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

A razão pela qual estou chamando undecorate acima é que o react-dnd usa componentes de ordem superior e queremos os componentes desembrulhados. Esta é uma complicação feia de componentes de ordem superior na minha opinião. Talvez você pudesse implementar isso enviando uma ação para o seu armazenamento de fluxo ou algo assim, mas no caso de você decidir fazer como eu, aqui está o meu undecorate :

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

  return curr;
}

Agora, se você implementar isso, tudo parecerá bem, exceto por um pequeno problema. A aula nunca vai embora! Ninguém chama setHoverState(null) (não existe uma função endHover em seu barTarget). A maneira adequada de lidar com isso é usar componentWillReceiveProps assim:

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

(Não se esqueça de adicionar { isFooOver: monitor.isOver() } em sua função de coleta)

Me diga como foi!

Muito obrigado pela explicação detalhada! Eu aviso você :)

Por fim, usei a abordagem Redux, então basicamente despachei um evento por meio de um redutor quando o evento drop foi disparado.

    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}))

Esta página foi útil?
0 / 5 - 0 avaliações