구성 요소 외부에있는 클릭 이벤트를 수신하는 방법


89

드롭 다운 구성 요소 외부에서 클릭이 발생하면 드롭 다운 메뉴를 닫고 싶습니다.

어떻게하나요?

답변:


59

추가 한 요소에서 다음 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가죽 그것.


그러나 이것은 클릭을 마우스와 구체적으로 연결합니다. 클릭 이벤트는 터치 이벤트와 엔터 키에 의해 생성 될 수 있습니다.이 솔루션은 반응 할 수 없으므로 모바일 및 접근성 목적에 적합하지 않습니다. = (
Mike 'Pomax'Kamermans dec

@ Mike'Pomax'Kamermans 이제 모바일 용 onTouchStart 및 onTouchEnd를 사용할 수 있습니다. facebook.github.io/react/docs/events.html#touch-events
naoufal

3
그것들은 오랫동안 존재 해 왔지만 안드로이드에서는 잘 작동하지 않을 것 preventDefault()입니다. 이벤트를 즉시 호출해야 하거나 React의 전처리가 방해하는 네이티브 Android 동작이 시작되기 때문입니다. 나는 이후로 npmjs.com/package/react-onclickoutside를 썼습니다 .
Mike 'Pomax'Kamermans

나는 그것을 사랑한다! 칭찬. mousedown에서 이벤트 리스너를 제거하면 도움이 될 것입니다. componentWillUnmount = () => window.removeEventListener ( 'mousedown', this.pageClick, false);
Juni Brosas 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


이렇게하면 문서에 하나가 추가되지만 구성 요소의 모든 클릭 이벤트도 문서 이벤트를 트리거 함을 의미합니다. 문서 리스너의 대상이 항상 구성 요소 자체가 아니라 구성 요소 끝에서 낮은 div이기 때문에 자체 문제가 발생합니다.
Allan Hortle 2014 년

귀하의 의견을 따르는 지 잘 모르겠지만 이벤트 리스너를 문서에 바인딩하면 선택기 (표준 이벤트 위임)를 일치 시키거나 다른 임의의 요구 사항 (예 : 다른 요소 내에 있지 않은 대상 요소 ).
i_like_robots 2014 년

3
이것은 @AllanHortle이 지적한 것처럼 문제를 일으 킵니다. 반응 이벤트의 stopPropagation은 문서 이벤트 핸들러가 이벤트를 수신하는 것을 막지 않습니다.
Matt Crinklaw-Vogt

4
관심있는 사람에게는 문서를 사용할 때 stopPropagation에 문제가있었습니다. 이벤트 리스너를 창에 연결하면이 문제가 해결되는 것 같습니까?
Titus

10
언급했듯이 this.getDOMNode ()는 더 이상 사용되지 않습니다. 대신이 같은 사용 ReactDOM : ReactDOM.findDOMNode (이) .contains (e.target)
아르네 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> 
        )
    }
});

이것이 많은 구성 요소에서 사용되는 경우 mixin을 사용하면 더 좋습니다.

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가 수정되었습니다.이 답변은 유용한 정보를 추가한다고 생각합니다. 아마도 귀하의 의견은 이제 제거 될 수 있습니다.
ja

잘못된 이유로 내 변경 사항을 되돌 렸습니다. this.refs.component0.14 facebook.github.io/react/blog/2015/07/03/…
Gajus

@GajusKuizinas-0.14가 최신 릴리스 (현재 베타)가되면 변경해도됩니다.
ja

달러는 무엇입니까?
pronebird

2
두 가지 뷰의 프레임 워크이기 때문에 React로 jQuery를 찌르는 것을 싫어합니다.
Abdennour TOUMI

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
onBlur이 경우 DIV 또한 사업부와 함께 작동 tabIndex속성
gorpacrate

나에게 이것은 가장 간단하고 사용하기 쉬운 솔루션이었습니다. 버튼 요소에 사용하고 매력처럼 작동합니다. 감사!
Amir5000

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

나는 그것이 나를 위해 작동하지 않았지만 대답 중 하나에 투표했습니다. 결국 저를이 솔루션으로 이끌었습니다. 작업 순서를 약간 변경했습니다. 나는 target에서 mouseDown을, target에서 mouseUp을 듣습니다. 둘 중 하나가 TRUE를 반환하면 모달을 닫지 않습니다. 클릭이 등록 되 자마자 어디에서나 두 부울 {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
}

귀하의 답변을 수정했습니다. 참조를 추가 하여 호출하는 handleClickOutside것과 관련하여 오류가 발생할 수 있습니다 . 그렇지 않으면 Uncaught ReferenceError가 발생합니다. handleClickOutside가 정의되지 않았습니다 .document.addEventListener()thiscomponentWillMount()
Muhammad Hannan

1

파티에 너무 늦었지만 드롭 다운을 닫는 관련 코드로 드롭 다운의 부모 요소에 흐림 이벤트를 설정하고 드롭 다운이 열려 있는지 확인하는 부모 요소에 mousedown 리스너를 연결하는 데 성공했습니다. 이벤트가 열려 있으면 이벤트 전파를 중지하여 흐림 이벤트가 트리거되지 않습니다.

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 ()는 내가 추론 할 수있는 한 호출 할 필요는 없지만 파이어 폭스는 어떤 이유로 든 그것 없이는 잘 플레이되지 않습니다. Chrome, Firefox 및 Safari에서 작동합니다.


0

나는 이것에 대해 더 간단한 방법을 찾았습니다.

onHide(this.closeFunction)모달 만 추가하면 됩니다

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

모달을 닫는 기능이 있다고 가정합니다.


-1

뛰어난 react-onclickoutside mixin을 사용하세요 .

npm install --save react-onclickoutside

그리고

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

4
FYI 믹스 인은 현재 React에서 더 이상 사용되지 않습니다.
machineghost
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.