Klick außerhalb der React-Komponente erkennen

Lesezeit: 10 Minuten

Klick auserhalb der React Komponente erkennen
Thijs Körselman

Ich suche nach einer Möglichkeit zu erkennen, ob ein Klickereignis außerhalb einer Komponente aufgetreten ist, wie in this beschrieben Artikel. jQuery next() wird verwendet, um zu sehen, ob das Ziel eines Klickereignisses das dom-Element als eines seiner Eltern hat. Wenn es eine Übereinstimmung gibt, gehört das Click-Ereignis zu einem der Kinder und wird daher nicht als außerhalb der Komponente liegend betrachtet.

Also möchte ich in meiner Komponente einen Click-Handler an das Fenster anhängen. Wenn der Handler feuert, muss ich das Ziel mit den dom-Kindern meiner Komponente vergleichen.

Das Click-Ereignis enthält Eigenschaften wie “Pfad”, die den Dom-Pfad zu enthalten scheinen, den das Ereignis zurückgelegt hat. Ich bin mir nicht sicher, was ich vergleichen oder wie ich es am besten durchqueren soll, und ich denke, jemand muss das bereits in eine clevere Hilfsfunktion gesteckt haben … Nein?

  • Könnten Sie den Click-Handler an das übergeordnete Element und nicht an das Fenster anhängen?

    – J. Mark Stevens

    13. September 2015 um 18:47 Uhr

  • Wenn Sie einen Click-Handler an das übergeordnete Element anhängen, wissen Sie, wann auf dieses Element oder eines seiner untergeordneten Elemente geklickt wird, aber ich muss alle erkennen andere Stellen, auf die geklickt wird, daher muss der Handler an das Fenster angehängt werden.

    – Thijs Koerselman

    13. September 2015 um 18:50 Uhr

  • Ich habe mir den Artikel nach der vorherigen Antwort angesehen. Wie wäre es, wenn Sie einen ClickState in der obersten Komponente festlegen und Klickaktionen von den Kindern weitergeben. Dann würden Sie die Requisiten in den Kindern überprüfen, um den Zustand „Öffnen und Schließen“ zu verwalten.

    – J. Mark Stevens

    13. September 2015 um 19:01 Uhr

  • Die oberste Komponente wäre meine App. Aber die Hörkomponente ist mehrere Ebenen tief und hat keine feste Position im Dom. Ich kann unmöglich allen Komponenten in meiner App Click-Handler hinzufügen, nur weil eine von ihnen wissen möchte, ob Sie irgendwo außerhalb davon geklickt haben. Andere Komponenten sollten sich dieser Logik nicht bewusst sein, da dies schreckliche Abhängigkeiten und Boilerplate-Code erzeugen würde.

    – Thijs Koerselman

    13. September 2015 um 19:14 Uhr


  • Ich möchte Ihnen eine sehr schöne Bibliothek empfehlen. Erstellt von AirBnb: github.com/airbnb/react-outside-click-handler

    – Osoischer Marcel

    5. April 2019 um 13:35 Uhr

React Redux und mapStateToProps verstehen
Pablo Barria Urenda

Aktualisierung 2021:

Es ist eine Weile her, seit ich diese Antwort hinzugefügt habe, und da sie immer noch auf Interesse zu stoßen scheint, dachte ich, ich würde sie auf eine aktuellere React-Version aktualisieren. Im Jahr 2021 würde ich diese Komponente so schreiben:

import React, { useState } from "react";
import "./DropDown.css";

export function DropDown({ options, callback }) {
    const [selected, setSelected] = useState("");
    const [expanded, setExpanded] = useState(false);

    function expand() {
        setExpanded(true);
    }

    function close() {
        setExpanded(false);
    }

    function select(event) {
        const value = event.target.textContent;
        callback(value);
        close();
        setSelected(value);
    }

    return (
        <div className="dropdown" tabIndex={0} onFocus={expand} onBlur={close} >
            <div>{selected}</div>
            {expanded ? (
                <div className={"dropdown-options-list"}>
                    {options.map((O) => (
                        <div className={"dropdown-option"} onClick={select}>
                            {O}
                        </div>
                    ))}
                </div>
            ) : null}
        </div>
    );
}

Ursprüngliche Antwort (2016):

Hier ist die Lösung, die für mich am besten funktioniert hat, ohne Ereignisse an den Container anzuhängen:

Bestimmte HTML-Elemente können das haben, was als “Fokus“, zum Beispiel Eingabeelemente. Diese Elemente reagieren auch auf die verwischen Ereignis, wenn sie diesen Fokus verlieren.

Um jedem Element die Möglichkeit zu geben, den Fokus zu haben, stellen Sie einfach sicher, dass sein tabindex-Attribut auf etwas anderes als -1 gesetzt ist. In normalem HTML wäre das durch das Setzen von tabindex Attribut, aber in React müssen Sie verwenden tabIndex (Beachte die Hauptstadt I).

Sie können dies auch über JavaScript mit tun element.setAttribute('tabindex',0)

Dafür habe ich es verwendet, um ein benutzerdefiniertes DropDown-Menü zu erstellen.

var DropDownMenu = React.createClass({
    getInitialState: function(){
        return {
            expanded: false
        }
    },
    expand: function(){
        this.setState({expanded: true});
    },
    collapse: function(){
        this.setState({expanded: false});
    },
    render: function(){
        if(this.state.expanded){
            var dropdown = ...; //the dropdown content
        } else {
            var dropdown = undefined;
        }
        
        return (
            <div className="dropDownMenu" tabIndex="0" onBlur={ this.collapse } >
                <div className="currentValue" onClick={this.expand}>
                    {this.props.displayValue}
                </div>
                {dropdown}
            </div>
        );
    }
});

  • Würde dies nicht zu Problemen mit der Zugänglichkeit führen? Da Sie ein zusätzliches Element fokussierbar machen, nur um zu erkennen, wann es in den Fokus geht, aber die Interaktion auf den Dropdown-Werten erfolgen sollte, nein?

    – Thijs Koerselman

    5. August 2016 um 8:06 Uhr

  • Ich habe dieses “Arbeiten” bis zu einem gewissen Grad, es ist ein cooler kleiner Hack. Mein Problem ist, dass mein Dropdown-Inhalt ist display:absolute damit das Dropdown die Größe des übergeordneten div nicht beeinflusst. Das bedeutet, wenn ich auf ein Element in der Dropdown-Liste klicke, wird der Onblur ausgelöst.

    – David

    7. Oktober 2016 um 17:37 Uhr

  • Ich hatte Probleme mit diesem Ansatz, kein Element im Dropdown-Inhalt löst keine Ereignisse wie onClick aus.

    – AnimaSola

    3. Januar 2017 um 5:56 Uhr

  • Sie können auf andere Probleme stoßen, wenn der Dropdown-Inhalt einige fokussierbare Elemente wie beispielsweise Formulareingaben enthält. Sie werden deinen Fokus stehlen, onBlur wird in Ihrem Dropdown-Container ausgelöst und expanded wird auf false gesetzt.

    – Kamagatos

    4. Juli 2017 um 4:47 Uhr


  • Informationen zum Klicken auf Elemente in Ihrem Ziel finden Sie in dieser Antwort: stackoverflow.com/a/44378829/642287

    – Onosa

    8. November 2018 um 15:56 Uhr

  • Dies sollte die akzeptierte Antwort sein. Funktionierte perfekt für Dropdown-Menüs mit absoluter Positionierung.

    – GeschirrspülerMitProgrammierfähigkeit

    13. Dezember 2017 um 17:53 Uhr

  • ReactDOM.findDOMNode und ist veraltet, sollte verwendet werden ref Rückrufe: github.com/yannickcr/eslint-plugin-react/issues/…

    – köstlich

    1. März 2018 um 13:09 Uhr

  • tolle und saubere lösung

    – Karim

    11. Mai 2018 um 11:55 Uhr

  • Das ist großartig, weil es wiederverwendbar ist. Perfekt.

    – Terraform

    18. November 2020 um 11:42 Uhr

  • Dies sollte die akzeptierte Antwort sein

    – Ziku

    12. Februar um 13:23 Uhr

1646312712 707 Klick auserhalb der React Komponente erkennen
Beau Smith

Nachdem ich hier viele Methoden ausprobiert hatte, entschied ich mich dafür github.com/Pomax/react-onclickoutside weil es so vollständig ist.

Ich habe das Modul über npm installiert und in meine Komponente importiert:

import onClickOutside from 'react-onclickoutside'

Dann habe ich in meiner Komponentenklasse die definiert handleClickOutside Methode:

handleClickOutside = () => {
  console.log('onClickOutside() method called')
}

Und beim Exportieren meiner Komponente habe ich sie eingepackt onClickOutside():

export default onClickOutside(NameOfComponent)

Das ist es.

  • Gibt es konkrete Vorteile gegenüber der tabIndex/onBlur Ansatz von Pablo vorgeschlagen? Wie funktioniert die Implementierung und wie unterscheidet sich ihr Verhalten von der onBlur sich nähern?

    – Mark Amery

    17. Januar 2017 um 15:20 Uhr

  • Es ist besser, eine dedizierte Komponente zu verwenden, als einen tabIndex-Hack zu verwenden. Upvote es!

    – Atul Yadav

    23. Januar 2017 um 18:55 Uhr

  • @MarkAmery – Ich habe einen Kommentar dazu abgegeben tabIndex/onBlur sich nähern. Es funktioniert nicht, wenn das Dropdown ist position:absolutebeispielsweise ein Menü, das über anderen Inhalten schwebt.

    – Beau Smith

    28. Januar 2017 um 0:11 Uhr

  • Ein weiterer Vorteil gegenüber Tabindex besteht darin, dass die Tabindex-Lösung auch ein Blur-Ereignis auslöst, wenn Sie sich auf ein untergeordnetes Element konzentrieren

    – Jemar Jones

    19. April 2017 um 19:24 Uhr

  • @BeauSmith – Vielen Dank! Meinen Tag gerettet!

    – Micha

    22. Januar 2020 um 16:07 Uhr

Hook-Implementierung basierend auf Tanner Linsley’s Excellent Vortrag auf der JSConf Hawaii 2020:

useOuterClick API

const Client = () => {
  const innerRef = useOuterClick(ev => {/*event handler code on outer click*/});
  return <div ref={innerRef}> Inside </div> 
};

Implementierung

function useOuterClick(callback) {
  const callbackRef = useRef(); // initialize mutable ref, which stores callback
  const innerRef = useRef(); // returned to client, who marks "border" element

  // update cb on each render, so second useEffect has access to current value 
  useEffect(() => { callbackRef.current = callback; });
  
  useEffect(() => {
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
    function handleClick(e) {
      if (innerRef.current && callbackRef.current && 
        !innerRef.current.contains(e.target)
      ) callbackRef.current(e);
    }
  }, []); // no dependencies -> stable click listener
      
  return innerRef; // convenience for client (doesn't need to init ref himself) 
}

Hier ist ein funktionierendes Beispiel:

/*
  Custom Hook
*/
function useOuterClick(callback) {
  const innerRef = useRef();
  const callbackRef = useRef();

  // set current callback in ref, before second useEffect uses it
  useEffect(() => { // useEffect wrapper to be safe for concurrent mode
    callbackRef.current = callback;
  });

  useEffect(() => {
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);

    // read most recent callback and innerRef dom node from refs
    function handleClick(e) {
      if (
        innerRef.current && 
        callbackRef.current &&
        !innerRef.current.contains(e.target)
      ) {
        callbackRef.current(e);
      }
    }
  }, []); // no need for callback + innerRef dep
  
  return innerRef; // return ref; client can omit `useRef`
}

/*
  Usage 
*/
const Client = () => {
  const [counter, setCounter] = useState(0);
  const innerRef = useOuterClick(e => {
    // counter state is up-to-date, when handler is called
    alert(`Clicked outside! Increment counter to ${counter + 1}`);
    setCounter(c => c + 1);
  });
  return (
    <div>
      <p>Click outside!</p>
      <div id="container" ref={innerRef}>
        Inside, counter: {counter}
      </div>
    </div>
  );
};

ReactDOM.render(<Client />, document.getElementById("root"));
#container { border: 1px solid red; padding: 20px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js" integrity="sha256-Ef0vObdWpkMAnxp39TYSLVS/vVUokDE8CDFnx7tjY6U=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js" integrity="sha256-p2yuFdE8hNZsQ31Qk+s8N+Me2fL5cc6NKXOC0U9uGww=" crossorigin="anonymous"></script>
<script> var {useRef, useEffect, useCallback, useState} = React</script>
<div id="root"></div>

Wichtige Punkte

  • useOuterClick nutzt veränderliche Verweise mager zu bieten Client API
  • stabil Klicken Sie auf den Listener für die Lebensdauer der enthaltenden Komponente ([] Tiefen)
  • Client kann Rückruf einstellen, ohne ihn auswendig lernen zu müssen useCallback
  • Callback-Body hat Zugriff auf die neuesten Requisiten und Status – keine veralteten Abschlusswerte

(Randbemerkung für iOS)

iOS behandelt im Allgemeinen nur bestimmte Elemente als anklickbar. Damit äußere Klicks funktionieren, wählen Sie einen anderen Klick-Listener als aus document – nichts nach oben inklusive body. Fügen Sie zB einen Listener im React-Root hinzu div und erweitern Sie seine Höhe, wie height: 100vh, um alle externen Klicks zu erfassen. Quelle: quirksmode.org

  • Gibt es konkrete Vorteile gegenüber der tabIndex/onBlur Ansatz von Pablo vorgeschlagen? Wie funktioniert die Implementierung und wie unterscheidet sich ihr Verhalten von der onBlur sich nähern?

    – Mark Amery

    17. Januar 2017 um 15:20 Uhr

  • Es ist besser, eine dedizierte Komponente zu verwenden, als einen tabIndex-Hack zu verwenden. Upvote es!

    – Atul Yadav

    23. Januar 2017 um 18:55 Uhr

  • @MarkAmery – Ich habe einen Kommentar dazu abgegeben tabIndex/onBlur sich nähern. Es funktioniert nicht, wenn das Dropdown ist position:absolutebeispielsweise ein Menü, das über anderen Inhalten schwebt.

    – Beau Smith

    28. Januar 2017 um 0:11 Uhr

  • Ein weiterer Vorteil gegenüber Tabindex besteht darin, dass die Tabindex-Lösung auch ein Blur-Ereignis auslöst, wenn Sie sich auf ein untergeordnetes Element konzentrieren

    – Jemar Jones

    19. April 2017 um 19:24 Uhr

  • @BeauSmith – Vielen Dank! Meinen Tag gerettet!

    – Micha

    22. Januar 2020 um 16:07 Uhr

[Update] Lösung mit Reagieren ^16.8 verwenden Haken

CodeSandbox

import React, { useEffect, useRef, useState } from 'react';

const SampleComponent = () => {
    const [clickedOutside, setClickedOutside] = useState(false);
    const myRef = useRef();

    const handleClickOutside = e => {
        if (!myRef.current.contains(e.target)) {
            setClickedOutside(true);
        }
    };

    const handleClickInside = () => setClickedOutside(false);

    useEffect(() => {
        document.addEventListener('mousedown', handleClickOutside);
        return () => document.removeEventListener('mousedown', handleClickOutside);
    });

    return (
        <button ref={myRef} onClick={handleClickInside}>
            {clickedOutside ? 'Bye!' : 'Hello!'}
        </button>
    );
};

export default SampleComponent;

Lösung mit Reagieren ^16.3:

CodeSandbox

import React, { Component } from "react";

class SampleComponent extends Component {
  state = {
    clickedOutside: false
  };

  componentDidMount() {
    document.addEventListener("mousedown", this.handleClickOutside);
  }

  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClickOutside);
  }

  myRef = React.createRef();

  handleClickOutside = e => {
    if (!this.myRef.current.contains(e.target)) {
      this.setState({ clickedOutside: true });
    }
  };

  handleClickInside = () => this.setState({ clickedOutside: false });

  render() {
    return (
      <button ref={this.myRef} onClick={this.handleClickInside}>
        {this.state.clickedOutside ? "Bye!" : "Hello!"}
      </button>
    );
  }
}

export default SampleComponent;

  • Das ist jetzt die richtige Lösung. Wenn Sie den Fehler erhalten .contains is not a functionkann es daran liegen, dass Sie das ref-Prop an eine benutzerdefinierte Komponente und nicht an ein echtes DOM-Element wie a übergeben <div>

    – willma

    14. November 2018 um 0:24 Uhr

  • Für diejenigen, die versuchen zu bestehen ref prop zu einer benutzerdefinierten Komponente, die Sie sich vielleicht ansehen möchten React.forwardRef

    – Onoja

    18. Juli 2019 um 5:44 Uhr

  • Das funktioniert wie ein Zauber und hat meinen Tag gerettet 🙂

    – Chen Ni

    26. Oktober 2021 um 10:15 Uhr

  • Wenn Sie auf die Bildlaufleiste klicken oder sie gedrückt halten, wirkt sich dies ebenfalls aus. Wie kann man einen Klick außerhalb erkennen lassen, aber nicht die Bildlaufleiste?

    – Kien

    2. Januar um 2:32

923670cookie-checkKlick außerhalb der React-Komponente erkennen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy