ウェブサイト検索

javascript - onClick は動作するが、React コンポーネントで onDoubleClick は無視される


私はReactでマインスイーパゲームを作っており、セルがシングルクリックされた場合とダブルクリックされた場合で異なるアクションを実行したいと考えています。現在、onDoubleClick 関数は一度も発火せず、onClick からのアラートが表示されます。もし onClick ハンドラーを削除すると、onDoubleClick は機能します。なぜ両方のイベントが機能しないのでしょうか?要素に両方のイベントを持たせることは可能でしょうか?

/** @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'));

回答 #1

これはReactの制限ではなく、DOMのclickおよびdblclickイベントの制限です。Quirksmodeのクリックのドキュメントで示唆されているように:

同じ要素に対して click と dblclick イベントを登録しないでください:単一クリックイベントと dblclick イベントにつながる click イベントを区別することは不可能です。

より最新のドキュメントについては、dblclick イベントに関する W3C の仕様書には以下のように記載されています:

ユーザーエージェントは、指示装置の主ボタンが要素上で2回クリックされたときに、このイベントを送出しなければなりません。

ダブルクリックイベントは、必ず2回のクリックイベントの後に発生します。

編集:

もう1つのおすすめの読み物は、jQueryのdblclickハンドラーです:

同じ要素に対して、クリックイベントとダブルクリックイベントの両方にハンドラをバインドすることは推奨されません。発生するイベントの順序はブラウザによって異なり、ダブルクリックの前に2回クリックイベントが発生する場合もあれば、1回しか発生しない場合もあります。ダブルクリックの感度(ダブルクリックとして認識されるクリック間の最大時間)は、オペレーティングシステムやブラウザによって異なり、多くの場合ユーザーが設定可能です。

回答 #2

<code>ondoubleclick</code> を使う代わりに、<code>event.detail</code> を使って現在のクリック回数を取得できます。これは、マウスが短時間に同じ場所でクリックされた回数です。

const handleClick = (e) => {
  switch (e.detail) {
    case 1:
      console.log("click");
      break;
    case 2:
      console.log("double click");
      break;
    case 3:
      console.log("triple click");
      break;
  }
};

return <button onClick={handleClick}>Click me</button>;

上の例では、ボタンをトリプルクリックすると、3つのケースすべてが表示されます:

click 
double click 
triple click 

ライブデモ

回答 #3

必要な結果は、通常のクリックアクションを発火させる際にわずかな遅延を与えることで達成できます。この遅延は、ダブルクリックイベントが発生したときにキャンセルされます。

  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>

回答 #4

このように、カスタムフックを使ってシンプルなクリックとダブルクリックを処理することができます:

import { useState, useEffect } from 'react';

function useSingleAndDoubleClick(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);
}

それから、あなたのコンポーネントで次のように使用できます:

const click = useSingleAndDoubleClick(callbackClick, callbackDoubleClick);
<button onClick={click}>clic</button>

回答 #5

編集:

これは React 0.15.3 では問題ではないことがわかりました。

オリジナル:

React 0.13.3 の場合、ここに二つの解決策があります。

1. 参照コールバック

注意してください。ダブルクリックの場合でも、シングルクリックのハンドラーは2回呼び出されます(各クリックごとに1回ずつ)。

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 のデバウンス

私は lodash を使った別の解決策を持っていましたが、複雑さのためにそれをあきらめました。この方法の利点は、「クリック」が一度だけ呼び出され、「ダブルクリック」の場合にはまったく呼び出されないことでした。

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;

説教台に立って

ダブルクリックが簡単には検出できないという考え方は理解していますが、良くも悪くも、それは存在するパラダイムであり、オペレーティングシステムでの普及によってユーザーが理解しているものです。さらに、それは現代のブラウザでもまだサポートされているパラダイムです。DOM仕様から削除されるまでは、私の意見では、ReactはonClickに並んで機能するonDoubleClickプロップをサポートすべきだと思います。それが現在サポートされていないように見えるのは残念です。

関連記事