onClick funktioniert, aber onDoubleClick wird in der React-Komponente ignoriert

70

Ich baue ein Minesweeper-Spiel mit React und möchte eine andere Aktion ausführen, wenn eine Zelle einfach oder doppelt angeklickt wird. Derzeit wird die onDoubleClickFunktion niemals ausgelöst, der Alarm von onClickwird angezeigt. Wenn ich den onClickHandler entferne , onDoubleClickfunktioniert. Warum funktionieren nicht beide Veranstaltungen? Ist es möglich, beide Ereignisse auf einem Element zu haben?

/** @jsx React.DOM */

var Mine = React.createClass({
  render: function(){
    return (
      <div className="mineBox" id={this.props.id} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}></div>
    )
  }
});

var MineRow = React.createClass({
  render: function(){
    var width = this.props.width,
        row = [];
    for (var i = 0; i < width; i++){
      row.push(<Mine id={String(this.props.row + i)} boxClass={this.props.boxClass} onDoubleClick={this.props.onDoubleClick} onClick={this.props.onClick}/>)
    }
    return (
      <div>{row}</div>
    )
  }
})

var MineSweeper = React.createClass({
  handleDoubleClick: function(){
    alert('Double Clicked');
  },
  handleClick: function(){
    alert('Single Clicked');
  },
  render: function(){
    var height = this.props.height,
        table = [];
    for (var i = 0; i < height; i++){
      table.push(<MineRow width={this.props.width} row={String.fromCharCode(97 + i)} onDoubleClick={this.handleDoubleClick} onClick={this.handleClick}/>)
    }
    return (
      <div>{table}</div>
    )
  }
})

var bombs = ['a0', 'b1', 'c2'];
React.renderComponent(<MineSweeper height={5} width={5} bombs={bombs}/>, document.getElementById('content'));
thisisnotabus
quelle
2
Da nach dem ersten Klick eine Warnung angezeigt wird, können Sie den zweiten Klick niemals ausführen, um das dblclickEreignis auszulösen . In meiner Antwort erfahren Sie, warum es nicht möglich ist, den ersten Klick zu verhindern.
Ross Allen
Wie wäre es mit einer Problemumgehung? Verfolgen Sie die Zeit zwischen den Klicks und erwägen Sie einen Doppelklick für zwei beliebige Klicks, die unter einer bestimmten Dauer auftreten, z. B. 600 ms.
KayleeFrye_onDeck

Antworten:

64

Dies ist keine Einschränkung von React, sondern eine Einschränkung der DOMs clickund dblclickEreignisse. Wie von Quirksmode vorgeschlagenen Klick Dokumentation :

Registrieren Sie keine Klick- und Doppelklickereignisse für dasselbe Element: Es ist unmöglich, Einzelklickereignisse von Klickereignissen zu unterscheiden, die zu einem Doppelklickereignis führen.

Eine aktuellere Dokumentation finden Sie in der W3C-Spezifikation des dblclickEreignisses :

Ein Benutzeragent muss dieses Ereignis auslösen, wenn die primäre Schaltfläche eines Zeigegeräts zweimal über ein Element geklickt wird.

Ein Doppelklickereignis tritt notwendigerweise nach zwei Klickereignissen auf.

Bearbeiten :

Eine weitere empfohlene Lektüre ist der dblclickHandler von jQuery :

Es wird nicht empfohlen, Handler für dasselbe Element an die Ereignisse click und dblclick zu binden. Die Reihenfolge der ausgelösten Ereignisse variiert von Browser zu Browser, wobei einige zwei Klickereignisse vor dem dblclick empfangen und andere nur eines. Die Doppelklickempfindlichkeit (maximale Zeit zwischen Klicks, die als Doppelklick erkannt wird) kann je nach Betriebssystem und Browser variieren und ist häufig vom Benutzer konfigurierbar.

Ross Allen
quelle
29

Das erforderliche Ergebnis kann erzielt werden, indem beim Auslösen der normalen Klickaktion eine geringfügige Verzögerung vorgesehen wird, die abgebrochen wird, wenn das Doppelklickereignis eintritt.

  let timer = 0;
  let delay = 200;
  let prevent = false;

  doClickAction() {
    console.log(' click');
  }
  doDoubleClickAction() {
    console.log('Double Click')
  }
  handleClick() {
    let me = this;
    timer = setTimeout(function() {
      if (!prevent) {
        me.doClickAction();
      }
      prevent = false;
    }, delay);
  }
  handleDoubleClick(){
    clearTimeout(timer);
    prevent = true;
    this.doDoubleClickAction();
  }
 < button onClick={this.handleClick.bind(this)} 
    onDoubleClick = {this.handleDoubleClick.bind(this)} > click me </button>
Akanksha Chaturvedi
quelle
Tolle Lösung, danke! Ich bin nur ein bisschen verwirrt: Ist es tatsächlich notwendig, sowohl "verhindern" als auch "clearTimeout ()" zu verwenden? Wäre nicht einer für diesen Fall ausreichend?
Nicole
16

Bearbeiten:

Ich habe festgestellt, dass dies kein Problem mit React 0.15.3 ist.


Original:

Für React 0.13.3 sind hier zwei Lösungen.

1. Ref Rückruf

Beachten Sie, dass der Einzelklick-Handler auch bei Doppelklicks zweimal aufgerufen wird (einmal für jeden Klick).

const ListItem = React.createClass({

  handleClick() {
    console.log('single click');
  },

  handleDoubleClick() {
    console.log('double click');
  },

  refCallback(item) {
    if (item) {
      item.getDOMNode().ondblclick = this.handleDoubleClick;
    }
  },

  render() {
    return (
      <div onClick={this.handleClick}
           ref={this.refCallback}>
      </div>
    );
  }
});

module.exports = ListItem;

2. lodash entprellen

Ich hatte eine andere Lösung, die verwendet wurde lodash, aber ich habe sie wegen der Komplexität aufgegeben. Dies hatte den Vorteil, dass "Klick" nur einmal aufgerufen wurde und bei "Doppelklick" überhaupt nicht.

import _ from 'lodash'

const ListItem = React.createClass({

  handleClick(e) {
    if (!this._delayedClick) {
      this._delayedClick = _.debounce(this.doClick, 500);
    }
    if (this.clickedOnce) {
      this._delayedClick.cancel();
      this.clickedOnce = false;
      console.log('double click');
    } else {
      this._delayedClick(e);
      this.clickedOnce = true;
    }
  },

  doClick(e) {
    this.clickedOnce = undefined;
    console.log('single click');
  },

  render() {
    return (
      <div onClick={this.handleClick}>
      </div>
    );
  }
});

module.exports = ListItem;

auf der Seifenkiste

Ich schätze die Idee , dass Doppelklick etwas nicht leicht zu erkennen, aber für besser oder schlechter es IST ein Paradigma , das und ein solches vorhanden ist, dass die Nutzer in Betriebssystemen aufgrund ihrer Prävalenz verstehen. Darüber hinaus ist es ein Paradigma, das moderne Browser immer noch unterstützen. Bis zu dem Zeitpunkt, an dem es aus den DOM-Spezifikationen entfernt wird, sollte React meiner Meinung nach eine funktionierende onDoubleClickRequisite unterstützen onClick. Es ist bedauerlich, dass dies anscheinend nicht der Fall ist.

Jeff Fairley
quelle
Wie ist es kein Problem in React 15.3?
Sassan
2
@Sassan Ich sehe dieses Problem auch nicht in der neuesten Version von react (15.6.1) - beide werden wie erwartet ausgelöst. Beachten Sie, dass beim Doppelklick der normale Klick-Handler auch doppelt ausgelöst wird, aber ich bin mir ziemlich sicher, dass dies zu erwarten ist.
Skipjack
6

Sie können einen benutzerdefinierten Haken verwenden, um einfaches Klicken und Doppelklicken wie folgt zu handhaben:

function useSimpleAndDoubleClick(actionSimpleClick, actionDoubleClick, delay = 250) {
    const [click, setClick] = useState(0);

    useEffect(() => {
        const timer = setTimeout(() => {
            // simple click
            if (click === 1) actionSimpleClick();
            setClick(0);
        }, delay);

        // the duration between this click and the previous one
        // is less than the value of delay = double-click
        if (click === 2) actionDoubleClick();

        return () => clearTimeout(timer);
        
    }, [click]);

    return () => setClick(prev => prev + 1);
}

Dann können Sie in Ihrer Komponente Folgendes verwenden:

const click = useSimpleAndDoubleClick(callbackClick, callbackDoubleClick);
<button onClick={click}>clic</button>
Erminea Nea
quelle
1
Dies ist definitiv die richtige Antwort für das Jahr 2020 :)
Mustafa Yilmaz
4

Folgendes habe ich getan. Verbesserungsvorschläge sind willkommen.

class DoubleClick extends React.Component {
  state = {counter: 0}

  handleClick = () => {
   this.setState(state => ({
    counter: this.state.counter + 1,
  }))
 }


  handleDoubleClick = () => {
   this.setState(state => ({
    counter: this.state.counter - 2,
  }))
 }

 render() {
   return(
   <>
    <button onClick={this.handleClick} onDoubleClick={this.handleDoubleClick>
      {this.state.counter}
    </button>
   </>
  )
 }
}
Alex Mireles
quelle
0

Dies ist die Lösung einer ähnlichen Schaltfläche mit Inkrement- und Unterscheidungswerten basierend auf der Lösung von Erminea.

useEffect(() => {
    let singleClickTimer;
    if (clicks === 1) {
      singleClickTimer = setTimeout(
        () => {
          handleClick();
          setClicks(0);
        }, 250);
    } else if (clicks === 2) {
      handleDoubleClick();
      setClicks(0);
    }
    return () => clearTimeout(singleClickTimer);
  }, [clicks]);

  const handleClick = () => {
    console.log('single click');
    total = totalClicks + 1;
    setTotalClicks(total);
  }

  const handleDoubleClick = () => {
    console.log('double click');
    if (total > 0) {
      total = totalClicks - 1;
    }
    setTotalClicks(total);
  }

  return (
    <div
      className="likeButton"
      onClick={() => setClicks(clicks + 1)}
    >
      Likes | {totalClicks}
    </div>
)
Manos
quelle
0

Hier ist ein Weg, um dasselbe mit Versprechungen zu erreichen. waitForDoubleClickGibt ein zurück, Promisedas nur aufgelöst wird, wenn kein Doppelklick ausgeführt wurde. Andernfalls wird es abgelehnt. Die Zeit kann angepasst werden.

    async waitForDoubleClick() {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        if (!this.state.prevent) {
          resolve(true);
        } else {
          reject(false);
        }
      }, 250);
      this.setState({ ...this.state, timeout, prevent: false })
    });
  }
  clearWaitForDoubleClick() {
    clearTimeout(this.state.timeout);
    this.setState({
      prevent: true
    });
  }
  async onMouseUp() {
    try {
      const wait = await this.waitForDoubleClick();
      // Code for sinlge click goes here.
    } catch (error) {
      // Single click was prevented.
      console.log(error)
    }
  }
DivineCoder
quelle