コンポーネントの外部にあるクリックイベントをリッスンする方法


89

ドロップダウンコンポーネントの外側でクリックが発生したときにドロップダウンメニューを閉じたいのですが。

それ、どうやったら出来るの?

回答:


58

要素では私が追加されているmousedownmouseup、このように:

onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}

次に、親でこれを行います:

componentDidMount: function () {
    window.addEventListener('mousedown', this.pageClick, false);
},

pageClick: function (e) {
  if (this.mouseIsDownOnCalendar) {
      return;
  }

  this.setState({
      showCal: false
  });
},

mouseDownHandler: function () {
    this.mouseIsDownOnCalendar = true;
},

mouseUpHandler: function () {
    this.mouseIsDownOnCalendar = false;
}

これshowCalは、true私の場合はカレンダーをfalse表示して非表示にするブール値です。


ただし、これはクリックを特にマウスに関連付けます。クリックイベントは、タッチイベントとEnterキーによっても生成される可能性があり、このソリューションはこれに反応できず、モバイルやアクセシビリティの目的には適しません=(
Mike 'Pomax' Kamermans

@ Mike'Pomax'KamermansモバイルでonTouchStartおよびonTouchEndを使用できるようになりました。facebook.github.io/react/docs/events.html#touch-events
naoufal

3
それらは長い間存在していましたがpreventDefault()、イベントをすぐに呼び出す必要があるか、ネイティブのAndroidの動作が作動し、Reactの前処理が妨げられるため、androidではうまく機能しません。それ以来、私はnpmjs.com/package/react-onclickoutsideを作成しました。
Mike 'Pomax' Kamermans

大好きです!表彰されました。マウスダウン時にイベントリスナーを削除すると役立ちます。componentWillUnmount =()=> window.removeEventListener( 'mousedown'、this.pageClick、false);
ジュニ・ブロサス2017

73

ライフサイクルメソッドを使用して、ドキュメントにイベントリスナーを追加および削除します。

React.createClass({
    handleClick: function (e) {
        if (this.getDOMNode().contains(e.target)) {
            return;
        }
    },

    componentWillMount: function () {
        document.addEventListener('click', this.handleClick, false);
    },

    componentWillUnmount: function () {
        document.removeEventListener('click', this.handleClick, false);
    }
});

このコンポーネントの48〜54行目を確認してください。https//github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54


これによりドキュメントに1が追加されますが、これはコンポーネントのクリックイベントもドキュメントイベントをトリガーすることを意味します。これは、ドキュメントリスナーのターゲットが常にコンポーネント自体ではなく、コンポーネントの最後にある下位のdivであるため、それ自体に問題があります。
Allan Hortle 2014年

私はあなたのコメントをフォローしているのかよくわかりませんが、イベントリスナーをドキュメントにバインドすると、セレクター(標準のイベントの委任)を照合するか、他の任意の要件(EGは別の要素内にないターゲット要素)。
i_like_robots 2014年

3
@AllanHortleが指摘するように、これは問題を引き起こします。反応イベントのstopPropagationは、ドキュメントイベントハンドラーがイベントを受信することを妨げません。
Matt Crinklaw-Vogt 2014

4
興味のある方には、ドキュメントの使用時にstopPropagationで問題が発生しました。イベントリスナーをウィンドウにアタッチすると、この問題が解決するようです?
タイタス

10
前述のとおり、this.getDOMNode()は廃止されました。代わりに次のようにReactDOMを使用します。ReactDOM.findDOMNode(this).contains(e.target)
Arne H. Bitubekk

17

イベントのターゲットを確認します。イベントがコンポーネントまたはコンポーネントの子である場合、クリックは内部にあります。それ以外の場合は外にありました。

React.createClass({
    clickDocument: function(e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            // Inside of the component.
        } else {
            // Outside of the component.
        }

    },
    componentDidMount: function() {
        $(document).bind('click', this.clickDocument);
    },
    componentWillUnmount: function() {
        $(document).unbind('click', this.clickDocument);
    },
    render: function() {
        return (
            <div ref='component'>
                ...
            </div> 
        )
    }
});

これを多くのコンポーネントで使用する場合は、ミックスインの方が適しています。

var ClickMixin = {
    _clickDocument: function (e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            this.clickInside(e);
        } else {
            this.clickOutside(e);
        }
    },
    componentDidMount: function () {
        $(document).bind('click', this._clickDocument);
    },
    componentWillUnmount: function () {
        $(document).unbind('click', this._clickDocument);
    },
}

こちらの例をご覧くださいhttps//jsfiddle.net/0Lshs7mg/1/


@ Mike'Pomax'Kamermansは修正されました。この回答は有用な情報を追加すると思います。おそらくコメントが削除される可能性があります。
2015

間違った理由で私の変更を元に戻しました。this.refs.component0.14のようDOM要素を参照しているfacebook.github.io/react/blog/2015/07/03/...
Gajus

@GajusKuizinas-0.14が最新リリース(現在はベータ版)になったときに変更を加えても問題ありません。
2015

ドルとは?
ベンシンクレア

2
jQueryは2つのビューのフレームワークであるため、ReactでjQueryを実行するのは嫌いです。
Abdennour TOUMI 2017

11

あなたの特定のユースケースでは、現在受け入れられている答えは少しオーバーエンジニアリングされています。ユーザーがドロップダウンリストからクリックしたときにリッスンする場合は、<select>コンポーネントを親要素として使用し、それにonBlurハンドラーをアタッチします。

このアプローチの唯一の欠点は、ユーザーが要素にすでにフォーカスを維持していることを前提とし、フォームコントロールに依存していることです(tabキーが要素にもフォーカスし、ぼかすことを考慮した場合、これは必要な場合とそうでない場合があります)。)-しかし、これらの欠点は実際には、より複雑なユースケースの制限にすぎません。その場合、より複雑なソリューションが必要になる場合があります。

 var Dropdown = React.createClass({

   handleBlur: function(e) {
     // do something when user clicks outside of this element
   },

   render: function() {
     return (
       <select onBlur={this.handleBlur}>
         ...
       </select>
     );
   }
 });

10
onBlurdivは、divがtabIndex属性を持っている場合にも機能します
gorpacrate

私にとってこれは私が最も簡単で使いやすいと思った解決策でした。私はそれをボタン要素に使用していて、魅力のように機能します。ありがとう!
Amir5000 2015

5

コンポーネントの外部で発生するイベント用の汎用イベントハンドラーreact-outside-eventを作成しました

実装自体は簡単です:

  • コンポーネントがマウントされると、イベントハンドラーがwindowオブジェクトにアタッチされます。
  • イベントが発生すると、コンポーネントは、イベントがコンポーネント内から発生したかどうかを確認します。そうでない場合はonOutsideEvent、ターゲットコンポーネントでトリガーされます。
  • コンポーネントがマウント解除されると、イベントハンドラーが検出されます。
import React from 'react';
import ReactDOM from 'react-dom';

/**
 * @param {ReactClass} Target The component that defines `onOutsideEvent` handler.
 * @param {String[]} supportedEvents A list of valid DOM event names. Default: ['mousedown'].
 * @return {ReactClass}
 */
export default (Target, supportedEvents = ['mousedown']) => {
    return class ReactOutsideEvent extends React.Component {
        componentDidMount = () => {
            if (!this.refs.target.onOutsideEvent) {
                throw new Error('Component does not defined "onOutsideEvent" method.');
            }

            supportedEvents.forEach((eventName) => {
                window.addEventListener(eventName, this.handleEvent, false);
            });
        };

        componentWillUnmount = () => {
            supportedEvents.forEach((eventName) => {
                window.removeEventListener(eventName, this.handleEvent, false);
            });
        };

        handleEvent = (event) => {
            let target,
                targetElement,
                isInside,
                isOutside;

            target = this.refs.target;
            targetElement = ReactDOM.findDOMNode(target);
            isInside = targetElement.contains(event.target) || targetElement === event.target;
            isOutside = !isInside;



            if (isOutside) {
                target.onOutsideEvent(event);
            }
        };

        render() {
            return <Target ref='target' {... this.props} />;
        }
    }
};

コンポーネントを使用するには、上位コンポーネントを使用してターゲットコンポーネントクラス宣言をラップし、処理するイベントを定義する必要があります。

import React from 'react';
import ReactDOM from 'react-dom';
import ReactOutsideEvent from 'react-outside-event';

class Player extends React.Component {
    onOutsideEvent = (event) => {
        if (event.type === 'mousedown') {

        } else if (event.type === 'mouseup') {

        }
    }

    render () {
        return <div>Hello, World!</div>;
    }
}

export default ReactOutsideEvent(Player, ['mousedown', 'mouseup']);

4

うまくいかなかったのに、答えの1つに投票しました。それは私をこの解決策に導いてしまいました。操作の順序を少し変更しました。ターゲットではmouseDownを、ターゲットではmouseUpをリッスンしています。それらのいずれかがTRUEを返す場合、モーダルは閉じません。クリックが登録されるとすぐに、どこでも、これらの2つのブール値{mouseDownOnModal、mouseUpOnModal}はfalseに戻されます。

componentDidMount() {
    document.addEventListener('click', this._handlePageClick);
},

componentWillUnmount() {
    document.removeEventListener('click', this._handlePageClick);
},

_handlePageClick(e) {
    var wasDown = this.mouseDownOnModal;
    var wasUp = this.mouseUpOnModal;
    this.mouseDownOnModal = false;
    this.mouseUpOnModal = false;
    if (!wasDown && !wasUp)
        this.close();
},

_handleMouseDown() {
    this.mouseDownOnModal = true;
},

_handleMouseUp() {
    this.mouseUpOnModal = true;
},

render() {
    return (
        <Modal onMouseDown={this._handleMouseDown} >
               onMouseUp={this._handleMouseUp}
            {/* other_content_here */}
        </Modal>
    );
}

これには、すべてのコードが親ではなく子コンポーネントに依存するという利点があります。これは、このコンポーネントを再利用するときにコピーする定型コードがないことを意味します。


3
  1. 画面全体に広がる固定レイヤーを作成します(.backdrop)。
  2. ターゲット要素(.target)を要素の外側に配置し、.backdropスタックインデックス(z-index)を大きくします。

次に、.backdrop要素をクリックすると、「.target要素の外側」と見なされます。

.click-overlay {
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    z-index: 1;
}

.target {
    position: relative;
    z-index: 2;
}

2

refこれを実現するためにsを使用できます。次のようなものが機能するはずです。

ref要素に追加します。

<div ref={(element) => { this.myElement = element; }}></div>

次に、要素の外でクリックを処理する関数を次のように追加できます。

handleClickOutside(e) {
  if (!this.myElement.contains(e)) {
    this.setState({ myElementVisibility: false });
  }
}

最後に、イベントリスナーを追加および削除して、マウントおよびアンマウントします。

componentWillMount() {
  document.addEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

componentWillUnmount() {
  document.removeEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

あなたの答えを変更し、それが呼び出しを参照してエラーの可能性を持っていたhandleClickOutsidedocument.addEventListener()追加することでthis、参照を。それ以外の場合は、Uncaught ReferenceErrorが発生します。handleClickOutsideが定義されていませんcomponentWillMount()
Muhammad Hannan

1

パーティーにはとても遅れましたが、ドロップダウンの親要素にblurイベントを設定し、関連付けられたコードでドロップダウンを閉じることと、ドロップダウンが開いているかどうかを確認するマウスダウンリスナーを親要素にアタッチすることに成功しましたイベントが開いている場合はイベントの伝播を停止し、ぼかしイベントがトリガーされないようにします。

mousedownイベントはこれをバブルアップするので、子でのmousedownが親でぼかしを引き起こすのを防ぎます。

/* Some react component */
...

showFoo = () => this.setState({ showFoo: true });

hideFoo = () => this.setState({ showFoo: false });

clicked = e => {
    if (!this.state.showFoo) {
        this.showFoo();
        return;
    }
    e.preventDefault()
    e.stopPropagation()
}

render() {
    return (
        <div 
            onFocus={this.showFoo}
            onBlur={this.hideFoo}
            onMouseDown={this.clicked}
        >
            {this.state.showFoo ? <FooComponent /> : null}
        </div>
    )
}

...

e.preventDefault()は、私が推論できる限り呼び出す必要はありませんが、Firefoxは何らかの理由でそれなしではうまく機能しません。Chrome、Firefox、Safariで動作します。


0

私はこれについてもっと簡単な方法を見つけました。

onHide(this.closeFunction)モーダルを追加するだけです

<Modal onHide={this.closeFunction}>
...
</Modal>

モーダルを閉じる関数があるとします。


-1

優れたreact-onclickoutsideミックスインを使用します。

npm install --save react-onclickoutside

その後

var Component = React.createClass({
  mixins: [
    require('react-onclickoutside')
  ],
  handleClickOutside: function(evt) {
    // ...handling code goes here... 
  }
});

4
FYIミックスインは、現在Reactでは非推奨です。
machineghost 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.