Bir bileşenin dışındaki tıklama olayları nasıl dinlenir?


89

Açılır menü bileşeninin dışında bir tıklama oluştuğunda bir açılır menüyü kapatmak istiyorum.

Bunu nasıl yaparım?

Yanıtlar:


59

Eleman ben eklemiş mousedownve mouseupbunun gibi:

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

Sonra ebeveynde bunu yapıyorum:

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

showCalBir boolean olduğunu zaman truebenim durumumda gösterileri takvim ve falsegizler onu.


bu, tıklamayı özellikle bir fareye bağlar. Tıklama olayları dokunma olayları tarafından oluşturulabilir ve tuşlar da girilebilir, bu çözümün tepki veremeyeceği, mobil ve erişilebilirlik amaçları için uygun değildir = (
Mike 'Pomax' Kamermans

@ Mike'Pomax'Kamermans Artık onTouchStart ve onTouchEnd'i mobil cihazlar için kullanabilirsiniz. facebook.github.io/react/docs/events.html#touch-events
naoufal

3
Bunlar uzun süredir var, ancak android ile iyi oynamayacaklar, çünkü preventDefault()olayları hemen aramanız gerekiyor ya da React'in ön işlemesi müdahale eden yerel Android davranışı devreye giriyor. O zamandan beri npmjs.com/package/react-onclickoutside yazdım .
Mike 'Pomax' Kamermans

Onu seviyorum! Övgüye değer. Fare altındayken olay dinleyicisini kaldırmak yardımcı olacaktır. componentWillUnmount = () => window.removeEventListener ('mousedown', this.pageClick, false);
Juni Brosas

73

Yaşam döngüsü yöntemlerini kullanarak, belgeye olay dinleyicileri ekleyin ve kaldırın.

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

Bu bileşenin 48-54. Satırlarına bakın: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54


Bu, belgeye bir tane ekler ancak bu, bileşendeki herhangi bir tıklama olayının da belge olayını tetiklediği anlamına gelir. Bu, kendi sorunlarına neden olur, çünkü belge dinleyicisinin hedefi, bileşenin kendisi değil, her zaman bir bileşenin sonundaki bazı alt bölümlerdir.
Allan Hortle

Yorumunuzu tam olarak takip ettiğimden emin değilim, ancak olay dinleyicilerini belgeye bağlarsanız, bir seçiciyi eşleştirerek (standart olay yetkilendirme) veya başka herhangi bir isteğe bağlı gereksinimle (örn. hedef öğe başka bir öğe içinde değil ).
i_like_robots

3
Bu, @AllanHortle'ın işaret ettiği gibi sorunlara neden oluyor. Bir react olayındaki stopPropagation, belge olay işleyicilerinin olayı almasını engellemez.
Matt Crinklaw-Vogt

4
İlgilenen herkes için belgeyi kullanırken stopPropagation ile ilgili sorunlar yaşıyordum. Olay dinleyicinizi pencereye eklerseniz, bu sorunu çözüyor gibi görünüyor?
Titus

10
Belirtildiği gibi this.getDOMNode () Kullanılmıyor. Bunun yerine böyle Kullanım ReactDOM: ReactDOM.findDOMNode (bu) .Contains (e.target)
Arne H. Bitubekk

17

Olayın hedefine bakın, eğer olay doğrudan bileşenin üzerindeyse veya bu bileşenin alt öğesindeyse tıklama içerideydi. Aksi takdirde dışarıdaydı.

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

Bu birçok bileşende kullanılacaksa, bir karışımla daha güzeldir:

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

Buradaki örneğe bakın: https://jsfiddle.net/0Lshs7mg/1/


@ Mike'Pomax'Kamermans düzeltildi, bu cevabın yararlı bilgiler eklediğini düşünüyorum, belki yorumunuz şimdi kaldırılabilir.
ja

Değişikliklerimi yanlış bir nedenle geri aldınız. this.refs.component0.14 facebook.github.io/react/blog/2015/07/03/…
Gajus

@GajusKuizinas - en son sürüm 0.14 olduğunda bu değişikliği yapmanın cezası var (şimdi beta).
ja

Dolar nedir?
pronebird

2
İki görüşün çerçevesi oldukları için jQuery'yi React ile karıştırmaktan nefret ediyorum.
Abdennour TOUMI

11

Özel kullanım durumunuz için, şu anda kabul edilen cevap biraz fazla tasarlanmış. Bir kullanıcı açılır listeden tıkladığında dinlemek istiyorsanız <select>, ana öğe olarak bir bileşen kullanın ve onBlurona bir işleyici ekleyin .

Bu yaklaşımın tek dezavantajı, kullanıcının öğeye zaten odaklanmayı sürdürdüğünü varsayması ve bir form kontrolüne dayanmasıdır ( tabanahtarın da öğelere odaklandığını ve bulanıklaştırdığını hesaba katarsanız istediğiniz şey olabilir veya olmayabilir. ) - ancak bu dezavantajlar, yalnızca daha karmaşık kullanım durumları için bir sınırdır, bu durumda daha karmaşık bir çözüm gerekli olabilir.

 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 tabIndexözelliği varsa div ile de çalışır
gorpacrate

Benim için bu, kullanımı en basit ve en kolay bulduğum çözümdü, onu bir düğme öğesinde kullanıyorum ve bir cazibe gibi çalışıyor. Teşekkürler!
Amir5000

5

Bileşenin dışından kaynaklanan olaylar için olay dışında tepki veren genel bir olay işleyicisi yazdım .

Uygulamanın kendisi basittir:

  • Bileşen monte edildiğinde, windownesneye bir olay işleyicisi eklenir .
  • Bir olay meydana geldiğinde bileşen, olayın bileşenin içinden kaynaklanıp kaynaklanmadığını kontrol eder. Aksi takdirde onOutsideEvent, hedef bileşende tetiklenir .
  • Bileşenin bağlantısı kesildiğinde, olay işleyicisi çıkarılır.
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} />;
        }
    }
};

Bileşeni kullanmak için, hedef bileşen sınıf bildirimini yüksek dereceli bileşeni kullanarak sarmalamanız ve işlemek istediğiniz olayları tanımlamanız gerekir:

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

Benim için işe yaramasa da cevaplardan birini oyladım. Beni bu çözüme götürdü. İşlemlerin sırasını biraz değiştirdim. Hedefte mouseDown ve hedefte mouseUp'ı dinliyorum. Bunlardan herhangi biri DOĞRU döndürürse, modu kapatmayız. Bir tıklama, herhangi bir yerde kaydedilir kaydedilmez, bu iki boole {mouseDownOnModal, mouseUpOnModal} false olarak geri döner.

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

Bunun avantajı, tüm kodun üst bileşen değil, alt bileşene ait olmasıdır. Bu, bu bileşeni yeniden kullanırken kopyalanacak standart kod olmadığı anlamına gelir.


3
  1. Tüm ekranı kaplayan sabit bir katman oluşturun ( .backdrop).
  2. Hedef öğeyi ( .target) öğenin dışında .backdropve daha büyük bir yığın dizini ( z-index) ile bulundurun.

Öğeye yapılan herhangi bir tıklama .backdrop" .targetöğenin dışında" olarak kabul edilecektir .

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

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

2

Kullanabilirsin ref başarmak s , aşağıdaki gibi bir şey çalışmalıdır.

Öğenize ekleyin ref:

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

Daha sonra, aşağıdaki gibi öğenin dışındaki tıklamayı işlemek için bir işlev ekleyebilirsiniz:

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

Son olarak, olay dinleyicilerini ekleyin ve kaldırın, eklenecek ve bağlantısı kesilecektir.

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
}

Cevabınızı Modifiye, aramadan referans olası hata vardı handleClickOutsideiçinde document.addEventListener()ekleyerek thisbaşvuru. Aksi takdirde verir Yakalanmayan ReferenceError: handleClickOutside tanımlı değil decomponentWillMount()
Muhammed Hannan

1

Partiye çok geç kaldı, ancak açılır listenin üst öğesinde, açılır listeyi kapatmak için ilişkili kodla bir bulanıklaştırma etkinliği ayarlayarak ve ayrıca açılır menünün açık olup olmadığını kontrol eden ana öğeye bir fare indirme dinleyicisi ekleyerek başarılı oldum ya da değil ve açıksa olay yayılmasını durduracak, böylece bulanıklık olayı tetiklenmeyecektir.

Fare düşürme olayı baloncuk oluşturduğundan, bu, çocuklar üzerinde herhangi bir fare düşürmenin ebeveyn üzerinde bulanıklığa neden olmasını önleyecektir.

/* 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 (), akıl yürütebildiğim kadarıyla çağrılmamalı, ancak firefox herhangi bir nedenle onsuz iyi oynamıyor. Chrome, Firefox ve Safari'de çalışır.


0

Bunun hakkında daha basit bir yol buldum.

Sadece onHide(this.closeFunction)modal eklemeniz gerekiyor

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

Modeli kapatmak için bir fonksiyonunuz olduğunu varsayarsak.


-1

Mükemmel tepki-onclickoutside karışımını kullanın :

npm install --save react-onclickoutside

Ve sonra

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

4
FYI mix-in'leri artık React'te kullanımdan kaldırıldı.
machineghost
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.