source

외부 리액트 컴포넌트 클릭 검출

nicesource 2022. 10. 15. 08:31
반응형

외부 리액트 컴포넌트 클릭 검출

기사에서 설명한 것처럼 클릭 이벤트가 컴포넌트 외부에서 발생했는지 여부를 검출하는 방법을 찾고 있습니다.jQuery nearst()는 클릭 이벤트의 타겟이 부모 중 하나로 dom 요소를 가지고 있는지 확인하기 위해 사용됩니다.일치하는 항목이 있는 경우 클릭 이벤트는 하위 항목 중 하나에 속하므로 구성 요소 외부에 있는 것으로 간주되지 않습니다.

제를 ' 핸들러'에 .window핸들러가 기동하면, 타겟과 컴포넌트의 돔 자식의 비교가 필요합니다.

클릭 이벤트에는 이벤트가 이동한 돔 경로를 유지하는 "경로"와 같은 속성이 포함됩니다.무엇을 비교해야 할지, 어떻게 건너야 할지 잘 모르겠어. 그리고 누군가 이미 그것을 훌륭한 유틸리티 기능에 넣었을 거라고 생각해.아니야?

다음 솔루션에서는 ES6를 사용하여 바인딩 및 메서드를 통한 참조 설정에 대한 베스트프랙티스를 따르고 있습니다.

동작 확인 방법:

후크의 실장:

import React, { useRef, useEffect } from "react";

/**
 * Hook that alerts clicks outside of the passed ref
 */
function useOutsideAlerter(ref) {
  useEffect(() => {
    /**
     * Alert if clicked on outside of element
     */
    function handleClickOutside(event) {
      if (ref.current && !ref.current.contains(event.target)) {
        alert("You clicked outside of me!");
      }
    }
    // Bind the event listener
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref]);
}

/**
 * Component that alerts if you click outside of it
 */
export default function OutsideAlerter(props) {
  const wrapperRef = useRef(null);
  useOutsideAlerter(wrapperRef);

  return <div ref={wrapperRef}>{props.children}</div>;
}

클래스 구현:

16.3 이후

import React, { Component } from "react";

/**
 * Component that alerts if you click outside of it
 */
export default class OutsideAlerter extends Component {
  constructor(props) {
    super(props);

    this.wrapperRef = React.createRef();
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

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

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

  /**
   * Alert if clicked on outside of element
   */
  handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.current.contains(event.target)) {
      alert("You clicked outside of me!");
    }
  }

  render() {
    return <div ref={this.wrapperRef}>{this.props.children}</div>;
  }
}

16.3 이전

import React, { Component } from "react";

/**
 * Component that alerts if you click outside of it
 */
export default class OutsideAlerter extends Component {
  constructor(props) {
    super(props);

    this.setWrapperRef = this.setWrapperRef.bind(this);
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

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

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

  /**
   * Set the wrapper ref
   */
  setWrapperRef(node) {
    this.wrapperRef = node;
  }

  /**
   * Alert if clicked on outside of element
   */
  handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
      alert("You clicked outside of me!");
    }
  }

  render() {
    return <div ref={this.setWrapperRef}>{this.props.children}</div>;
  }
}

2021 업데이트:

이 응답을 추가한 지 꽤 지났는데, 아직 관심을 끌고 있는 것 같기 때문에 최신 React 버전으로 업데이트하려고 합니다.2021년에는 이 컴포넌트를 다음과 같이 기술합니다.

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

답변(2016년):

컨테이너에 이벤트를 첨부하지 않고 가장 잘 작동한 솔루션은 다음과 같습니다.

특정 HTML 요소에는 입력 요소 "포커스"라고 알려진 것이 있을 수 있습니다.이러한 요소들은 초점을 잃으면 흐릿한 이벤트에도 반응합니다.

요소에 포커스를 둘 수 있는 용량을 부여하려면 해당 tabindex 속성이 -1 이외의 값으로 설정되어 있는지 확인하십시오.에서는, 「HTML」을 합니다.tabindex, 에서는 "React"를해야 합니다.tabIndex(大문자)에 해 주세요.I를 참조해 주세요.

하여 ""를 할 수도 .element.setAttribute('tabindex',0)

이것이 바로 커스텀 드롭다운 메뉴를 만들기 위해 사용하던 것입니다.

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

을 사용하다여기 파티에는 조금 늦었지만, 나에겐 이게 정말 좋은 해결책이야.그것이 다른 사람에게 도움이 되기를 바랍니다.당신은 Import가 필요합니다를 가져와야 합니다.findDOMNode부터에서react-dom

import ReactDOM from 'react-dom';
// ... ✂

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

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

handleClickOutside = event => {
    const domNode = ReactDOM.findDOMNode(this);

    if (!domNode || !domNode.contains(event.target)) {
        this.setState({
            visible: false
        });
    }
}

리액트 훅 어프로치(16.8+)

당신은재사용 가능한수 있습니다 만들 후크를 재사용 가능한 고리라고 불리는을 만들 수 있습니다.useComponentVisible..

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

export default function useComponentVisible(initialIsVisible) {
    const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
    const ref = useRef(null);

    const handleClickOutside = (event) => {
        if (ref.current && !ref.current.contains(event.target)) {
            setIsComponentVisible(false);
        }
    };

    useEffect(() => {
        document.addEventListener('click', handleClickOutside, true);
        return () => {
            document.removeEventListener('click', handleClickOutside, true);
        };
    }, []);

    return { ref, isComponentVisible, setIsComponentVisible };
}

그런 다음 컴포넌트에서 다음 작업을 수행하는 기능을 추가합니다.

const DropDown = () => {
    const { ref, isComponentVisible } = useComponentVisible(true);
    return (
       <div ref={ref}>
          {isComponentVisible && (<p>Dropdown Component</p>)}
       </div>
    );
 
}

여기서 코드 상자 예를 찾아보세요.

여기서 여러 가지 방법을 시도해 본 후, github.com/Pomax/react-onclickoutside이 완성되어 있기 때문에 그것을 이용하기로 결정했습니다.

npm을 통해 모듈을 설치하고 컴포넌트에 Import했습니다.

import onClickOutside from 'react-onclickoutside'

그러자, 나의 구성 요소 클래스에서 나는 다음으로클래스에서 정의했습니다 컴포넌트 정의했다.handleClickOutside메서드방법:

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

그리고 부품은 수출하는 나는또한 컴포넌트를로 싸고때 컴포넌트를 내보낼.onClickOutside()::

export default onClickOutside(NameOfComponent)

바로 그겁니다.

JSConf Hawaii 2020에서 Tanner Linsley의 뛰어난 강연에 기초한 후크 구현:

useOuterClickAPIAPI

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

실행

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

다음으로 작업 예를 제시하겠습니다.

/*
  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>

요점

  • useOuterClick변이 가능한 참조를 사용하여 희박하게 됩니다.ClientAPIAPI
  • 포함된 컴포넌트의 라이프 타임 동안 안정적인 클릭 리스너([]deps)디프)
  • Client메모 없이 콜백을설정할 수 있습니다로 memoize 필요 없이 콜백 정할 수 있다.useCallback
  • 콜백 본문은 최신 소품 및 상태에 액세스할 수 있습니다.오래된 폐쇄 값은 없습니다.

(iOS용 사이드 노트)

IOS일반적으로 간식 clickable.i로 오직 특정한 요소이다.OS는 일반적으로 특정 요소만 클릭 가능한 것으로 취급합니다.하려면 " " " 이외의 합니다.document안 돼 - 위로는 안 돼body를 추가합니다.div 하면 됩니다.height: 100vh외부 클릭을 모두 잡을 수 있습니다.출처 : quirksmode.org

[갱신] 후크를 사용 리액트 ^16.8 솔루션

코드 샌드박스

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;

React를 사용한 솔루션 ^16.3:

코드 샌드박스

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;

여기 있는 다른 대답들은 나에게 통하지 않았다.흐릿한 부분에서는 팝업을 숨기려고 했지만, 컨텐츠가 확실히 배치되어 있기 때문에, 안쪽의 컨텐츠의 클릭에도 onBlur가 점등하고 있었습니다.

다음은 나에게 효과가 있었던 접근법입니다.

// Inside the component:
onBlur(event) {
    // currentTarget refers to this component.
    // relatedTarget refers to the element where the user clicked (or focused) which
    // triggered this event.
    // So in effect, this condition checks if the user clicked outside the component.
    if (!event.currentTarget.contains(event.relatedTarget)) {
        // do your thing.
    }
},

이게 도움이 됐으면 좋겠다.

discuss.reactjs.org의 Ben Alpert 덕분에 해결책을 찾았습니다.제안된 접근법은 문서에 핸들러를 첨부하지만 문제가 있는 것으로 판명되었습니다.트리의 컴포넌트 중 하나를 클릭하면 업데이트 시 클릭된 요소가 제거되는 렌더가 생성되었습니다.React에서 렌더는 문서 본문 처리기가 호출되기 전에 발생하므로 요소가 트리 "내부"로 탐지되지 않았습니다.

이에 대한 해결책은 애플리케이션 루트 요소에 핸들러를 추가하는 것이었습니다.

메인:

window.__myapp_container = document.getElementById('app')
React.render(<App/>, window.__myapp_container)

컴포넌트:

import { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';

export default class ClickListener extends Component {

  static propTypes = {
    children: PropTypes.node.isRequired,
    onClickOutside: PropTypes.func.isRequired
  }

  componentDidMount () {
    window.__myapp_container.addEventListener('click', this.handleDocumentClick)
  }

  componentWillUnmount () {
    window.__myapp_container.removeEventListener('click', this.handleDocumentClick)
  }

  /* using fat arrow to bind to instance */
  handleDocumentClick = (evt) => {
    const area = ReactDOM.findDOMNode(this.refs.area);

    if (!area.contains(evt.target)) {
      this.props.onClickOutside(evt)
    }
  }

  render () {
    return (
      <div ref='area'>
       {this.props.children}
      </div>
    )
  }
}

에즈 방식...

const componentRef = useRef();

useEffect(() => {
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
    function handleClick(e: any) {
        if(componentRef && componentRef.current){
            const ref: any = componentRef.current
            if(!ref.contains(e.target)){
                // put your action here
            }
        }
    }
}, []);

다음으로 컴포넌트에 ref를 붙입니다.

<div ref={componentRef as any}> My Component </div>

MUI에는 이 문제를 해결하기 위한 작은 컴포넌트(https://mui.com/base/react-click-away-listener/)가 있습니다.이 컴포넌트는 체리픽을 할 수 있습니다.무게는 1kB 이하이며 모바일, IE 11 및 포털을 지원합니다.

대체 방법:

const onClickOutsideListener = () => {
    alert("click outside")
    document.removeEventListener("click", onClickOutsideListener)
  }

...

return (
  <div
    onMouseLeave={() => {
          document.addEventListener("click", onClickOutsideListener)
        }}
  >
   ...
  </div>

제 접근방식은 다음과 같습니다(https://jsfiddle.net/agymay93/4/):

이 특별한 컴포넌트를 만들었습니다.WatchClickOutside(내 생각에) 다음과 같이 사용할 수 있습니다.JSX구문:

<WatchClickOutside onClickOutside={this.handleClose}>
  <SomeDropdownEtc>
</WatchClickOutside>

여기 코드가 있습니다.WatchClickOutside컴포넌트:

import React, { Component } from 'react';

export default class WatchClickOutside extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  componentWillMount() {
    document.body.addEventListener('click', this.handleClick);
  }

  componentWillUnmount() {
    // remember to remove all events to avoid memory leaks
    document.body.removeEventListener('click', this.handleClick);
  }

  handleClick(event) {
    const {container} = this.refs; // get container that we'll wait to be clicked outside
    const {onClickOutside} = this.props; // get click outside callback
    const {target} = event; // get direct click event target

    // if there is no proper callback - no point of checking
    if (typeof onClickOutside !== 'function') {
      return;
    }

    // if target is container - container was not clicked outside
    // if container contains clicked target - click was not outside of it
    if (target !== container && !container.contains(target)) {
      onClickOutside(event); // clicked outside - fire callback
    }
  }

  render() {
    return (
      <div ref="container">
        {this.props.children}
      </div>
    );
  }
}

절대적인 포지셔닝이 필요한 사용자를 위해 선택한 간단한 옵션은 페이지 전체를 투명한 배경으로 덮도록 스타일링된 래퍼 컴포넌트를 추가하는 것입니다.그런 다음 onClick 이 요소를 클릭하여 내부 구성 요소를 닫을 수 있습니다.

<div style={{
        position: 'fixed',
        top: '0', right: '0', bottom: '0', left: '0',
        zIndex: '1000',
      }} onClick={() => handleOutsideClick()} >
    <Content style={{position: 'absolute'}}/>
</div>

현재와 같이 콘텐츠에 클릭핸들러를 추가하면 이벤트도 상위 div로 전파되므로 handler Outside Click이 트리거됩니다.이것이 바람직하지 않은 동작일 경우 핸들러에서 이벤트프로포그레이션을 정지하기만 하면 됩니다.

<Content style={{position: 'absolute'}} onClick={e => {
                                          e.stopPropagation();
                                          desiredFunctionCall();
                                        }}/>

`

여기에는 이미 많은 해답이 포함되어 있지만 대처하지 못하고 있습니다.e.stopPropagation()닫는 요소 외부의 리액트 링크를 클릭하지 않도록 합니다.

React에는 자체 인공 이벤트 핸들러가 있기 때문에 문서를 이벤트 청취자의 기준으로 사용할 수 없습니다.할 필요가 있다e.stopPropagation()그 전에 React는 문서 자체를 사용하기 때문입니다.예를 들어 를 사용하는 경우document.querySelector('body')대신.리액트 링크에서 클릭을 방지할 수 있습니다.다음은 click outside와 close를 구현하는 방법의 예입니다.
여기에는 ES6React 16.3이 사용됩니다.

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isOpen: false,
    };

    this.insideContainer = React.createRef();
  }

  componentWillMount() {
    document.querySelector('body').addEventListener("click", this.handleClick, false);
  }

  componentWillUnmount() {
    document.querySelector('body').removeEventListener("click", this.handleClick, false);
  }

  handleClick(e) {
    /* Check that we've clicked outside of the container and that it is open */
    if (!this.insideContainer.current.contains(e.target) && this.state.isOpen === true) {
      e.preventDefault();
      e.stopPropagation();
      this.setState({
        isOpen: false,
      })
    }
  };

  togggleOpenHandler(e) {
    e.preventDefault();

    this.setState({
      isOpen: !this.state.isOpen,
    })
  }

  render(){
    return(
      <div>
        <span ref={this.insideContainer}>
          <a href="#open-container" onClick={(e) => this.togggleOpenHandler(e)}>Open me</a>
        </span>
        <a href="/" onClick({/* clickHandler */})>
          Will not trigger a click when inside is open.
        </a>
      </div>
    );
  }
}

export default App;
import { useClickAway } from "react-use";

useClickAway(ref, () => console.log('OUTSIDE CLICKED'));

저는 이것을 따르면서 리액트 공식 문서에 따라 리액트해야 하는 리액트 ^16.3을 처리함으로써 이 작업을 부분적으로 수행했습니다.여기서 다른 제안들을 시도해 본 후에 효과가 있었던 것은 이것뿐입니다.

class App extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  componentWillMount() {
    document.addEventListener("mousedown", this.handleClick, false);
  }
  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClick, false);
  }
  handleClick = e => {
    /*Validating click is made inside a component*/
    if ( this.inputRef.current === e.target ) {
      return;
    }
    this.handleclickOutside();
  };
  handleClickOutside(){
    /*code to handle what to do when clicked outside*/
  }
  render(){
    return(
      <div>
        <span ref={this.inputRef} />
      </div>
    )
  }
}

이것이 문제를 해결하는 나의 방법이다.

커스텀 훅에서 부울값을 반환하고 이 값이 변경되었을 때(클릭이 arg로 전달된 ref를 벗어난 경우) useEffect 훅으로 이 변경을 파악할 수 있습니다.

다음은 라이브 예시입니다.Live Example on codes and box

import { useEffect, useRef, useState } from "react";

const useOutsideClick = (ref) => {
  const [outsieClick, setOutsideClick] = useState(null);

  useEffect(() => {
    const handleClickOutside = (e) => {
      if (!ref.current.contains(e.target)) {
        setOutsideClick(true);
      } else {
        setOutsideClick(false);
      }

      setOutsideClick(null);
    };

    document.addEventListener("mousedown", handleClickOutside);

    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref]);

  return outsieClick;
};

export const App = () => {
  const buttonRef = useRef(null);
  const buttonClickedOutside = useOutsideClick(buttonRef);

  useEffect(() => {
    // if the the click was outside of the button
    // do whatever you want
    if (buttonClickedOutside) {
      alert("hey you clicked outside of the button");
    }
  }, [buttonClickedOutside]);

  return (
    <div className="App">
      <button ref={buttonRef}>click outside me</button>
    </div>
  );
}

Ben Bud에 의해 인정된 답변을 연장하기 위해 스타일 컴포넌트를 사용하고 있는 경우, 그 방법으로 ref를 건네주면 다음과 같은 오류가 발생합니다.wrapperRef.contains는 함수가 아닙니다."

코멘트에서는 스타일링된 컴포넌트를 div로 감싸 ref를 전달하기 위한 권장 수정이 효과적입니다.그러나 문서에서는 이미 이 원인과 스타일 컴포넌트 내의 적절한 ref 사용에 대해 설명하고 있습니다.

참조 프로포트를 스타일 컴포넌트에 전달하면 Styled Component 래퍼 인스턴스가 제공되지만 기본 DOM 노드에는 제공되지 않습니다.이는 ref가 작동하는 방식에 기인합니다.포커스와 같은 DOM 메서드를 패키지에 직접 호출할 수 없습니다.실제 랩된 DOM 노드에 대한 참조를 얻으려면 대신 innerRef 프로펠러에 콜백을 전달합니다.

다음과 같은 경우:

<StyledDiv innerRef={el => { this.el = el }} />

그런 다음 "handle Click Outside" 기능에서 직접 액세스할 수 있습니다.

handleClickOutside = e => {
    if (this.el && !this.el.contains(e.target)) {
        console.log('clicked outside')
    }
}

이는 "onBlur" 접근법에도 적용됩니다.

componentDidMount(){
    this.el.focus()
}
blurHandler = () => {
    console.log('clicked outside')
}
render(){
    return(
        <StyledDiv
            onBlur={this.blurHandler}
            tabIndex="0"
            innerRef={el => { this.el = el }}
        />
    )
}

후크가 있는 타이프 스크립트

주의: React.createRef와 함께 React 버전 16.3을 사용하고 있습니다.다른 버전에서는 ref 콜백을 사용합니다.

드롭다운 구성 요소:

interface DropdownProps {
 ...
};

export const Dropdown: React.FC<DropdownProps> () {
  const ref: React.RefObject<HTMLDivElement> = React.createRef();
  
  const handleClickOutside = (event: MouseEvent) => {
    if (ref && ref !== null) {
      const cur = ref.current;
      if (cur && !cur.contains(event.target as Node)) {
        // close all dropdowns
      }
    }
  }

  useEffect(() => {
    // Bind the event listener
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };
  });

  return (
    <div ref={ref}>
        ...
    </div>
  );
}

비슷한 문제에 직면했지만, 여기서 선택한 답변이 작동하지 않았습니다.이치하다이것은 문서의 일부입니다.해도 """가 .handleClickOutside 새로 추가해야만 했습니다.ref과 이 버튼에 대해서!menuBtnRef.current.contains(e.target)처럼 같은 겪고 예요.나처럼 같은 문제를 겪고 있는 사람이 있다면 그냥 두고 갈 거야.

현재의 컴포넌트는 다음과 같습니다.


const Component = () => {

    const [isDropdownOpen, setIsDropdownOpen] = useState(false);
    const menuRef     = useRef(null);
    const menuBtnRef  = useRef(null);

    const handleDropdown = (e) => {
        setIsDropdownOpen(!isDropdownOpen);
    }

    const handleClickOutside = (e) => {
        if (menuRef.current && !menuRef.current.contains(e.target) && !menuBtnRef.current.contains(e.target)) {
            setIsDropdownOpen(false);
        }
    }

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

    return (

           <button ref={menuBtnRef} onClick={handleDropdown}></button>

           <div ref={menuRef} className={`${isDropdownOpen ? styles.dropdownMenuOpen : ''}`}>
                // ...dropdown items
           </div>
    )
}

모듈을 사용했습니다(작성자와 연관성이 없습니다).

npm install react-onclickout --save

const ClickOutHandler = require('react-onclickout');
 
class ExampleComponent extends React.Component {
 
  onClickOut(e) {
    if (hasClass(e.target, 'ignore-me')) return;
    alert('user clicked outside of the component!');
  }
 
  render() {
    return (
      <ClickOutHandler onClickOut={this.onClickOut}>
        <div>Click outside of me!</div>
      </ClickOutHandler>
    );
  }
}

그것은 그 일을 잘 했다.

타이프스크립트 + @ford04 제안서의 간략화 버전:

useOuterClick

const Client = () => {
  const ref = useOuterClick<HTMLDivElement>(e => { /* Custom-event-handler */ });
  return <div ref={ref}> Inside </div> 
};

실행

export default function useOuterClick<T extends HTMLElement>(callback: Function) {
  const callbackRef = useRef<Function>(); // initialize mutable ref, which stores callback
  const innerRef = useRef<T>(null); // 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", _onClick);
    return () => document.removeEventListener("click", _onClick);
    function _onClick(e: any): void {
      const clickedOutside = !(innerRef.current?.contains(e.target));
      if (clickedOutside)
        callbackRef.current?.(e);
    }
  }, []); // no dependencies -> stable click listener

  return innerRef; // convenience for client (doesn't need to init ref himself)
}

mui(material-ui)의 Click Away Listener로 간단하게:

<ClickAwayListener onClickAway={handleClickAway}>
    {children}
<ClickAwayListener >

자세한 것은, https://mui.com/base/react-click-away-listener/ 를 참조해 주세요.

타이프 인쇄를 해서.

function Tooltip(): ReactElement {
  const [show, setShow] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    function handleClickOutside(event: MouseEvent): void {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        setShow(false);
      }
    }
    // Bind the event listener
    document.addEventListener('mousedown', handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener('mousedown', handleClickOutside);
    };
  });

  return (
    <div ref={ref}></div>
  ) 
 }

다른 모든 답변에서 가장 우려되는 점은 클릭 이벤트를 루트/부모에서 아래로 필터링해야 한다는 것입니다.가장 쉬운 방법은 형제 요소를 fixed로 설정하고 드롭다운 뒤에 z-index 1로 설정하여 동일한 컴포넌트 내의 고정 요소에서 클릭 이벤트를 처리하는 것입니다.모든 것을 특정 컴포넌트에 집중시킵니다.

코드 예시

#HTML
<div className="parent">
  <div className={`dropdown ${this.state.open ? open : ''}`}>
    ...content
  </div>
  <div className="outer-handler" onClick={() => this.setState({open: false})}>
  </div>
</div>

#SASS
.dropdown {
  display: none;
  position: absolute;
  top: 0px;
  left: 0px;
  z-index: 100;
  &.open {
    display: block;
  }
}
.outer-handler {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0;
    z-index: 99;
    display: none;
    &.open {
      display: block;
    }
}
componentWillMount(){

  document.addEventListener('mousedown', this.handleClickOutside)
}

handleClickOutside(event) {

  if(event.path[0].id !== 'your-button'){
     this.setState({showWhatever: false})
  }
}

★★★path[0] 항목입니다.

Use On Click Outside Hook - 반응 16.8 +

일반적인 useOnOutsideClick 함수를 만듭니다.

export const useOnOutsideClick = handleOutsideClick => {
  const innerBorderRef = useRef();

  const onClick = event => {
    if (
      innerBorderRef.current &&
      !innerBorderRef.current.contains(event.target)
    ) {
      handleOutsideClick();
    }
  };

  useMountEffect(() => {
    document.addEventListener("click", onClick, true);
    return () => {
      document.removeEventListener("click", onClick, true);
    };
  });

  return { innerBorderRef };
};

const useMountEffect = fun => useEffect(fun, []);

다음으로 기능하는 컴포넌트에 후크를 사용합니다.

const OutsideClickDemo = ({ currentMode, changeContactAppMode }) => {

  const [open, setOpen] = useState(false);
  const { innerBorderRef } = useOnOutsideClick(() => setOpen(false));

  return (
    <div>
      <button onClick={() => setOpen(true)}>open</button>
      {open && (
        <div ref={innerBorderRef}>
           <SomeChild/>
        </div>
      )}
    </div>
  );

};

데모 링크

@pau1fitzgerald 답변에서 부분적으로 영감을 받았습니다.

드롭 다운의 경우는, Ben Bud의 솔루션은 정상적으로 동작했지만, onClick 핸들러가 있는 다른 토글 버튼이 있었습니다.따라서 외부 클릭 로직이 onClick 토글러 버튼과 충돌합니다.버튼의 ref도 전달하여 해결한 방법은 다음과 같습니다.

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

/**
 * Hook that triggers onClose when clicked outside of ref and buttonRef elements
 */
function useOutsideClicker(ref, buttonRef, onOutsideClick) {
  useEffect(() => {

    function handleClickOutside(event) {
      /* clicked on the element itself */
      if (ref.current && !ref.current.contains(event.target)) {
        return;
      }

      /* clicked on the toggle button */
      if (buttonRef.current && !buttonRef.current.contains(event.target)) {
        return;
      }

      /* If it's something else, trigger onClose */
      onOutsideClick();
    }

    // Bind the event listener
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref]);
}

/**
 * Component that alerts if you click outside of it
 */
export default function DropdownMenu(props) {
  const wrapperRef = useRef(null);
  const buttonRef = useRef(null);
  const [dropdownVisible, setDropdownVisible] = useState(false);

  useOutsideClicker(wrapperRef, buttonRef, closeDropdown);

  const toggleDropdown = () => setDropdownVisible(visible => !visible);

  const closeDropdown = () => setDropdownVisible(false);

  return (
    <div>
      <button onClick={toggleDropdown} ref={buttonRef}>Dropdown Toggler</button>
      {dropdownVisible && <div ref={wrapperRef}>{props.children}</div>}
    </div>
  );
}

전략의 예

컴포넌트 주위에 래퍼를 만들어 동일한 작업을 수행하는 솔루션 제공이 마음에 듭니다.

이것은 오히려 행동에 가깝기 때문에 저는 전략을 생각하고 다음과 같이 생각했습니다.

React를 처음 사용하는 사용자로, 사용 사례에서 보일러 플레이트를 절약하기 위해 도움이 필요합니다.

검토 후 의견을 들려주세요.

[아웃사이드]를 클릭합니다.행동

import ReactDOM from 'react-dom';

export default class ClickOutsideBehavior {

  constructor({component, appContainer, onClickOutside}) {

    // Can I extend the passed component's lifecycle events from here?
    this.component = component;
    this.appContainer = appContainer;
    this.onClickOutside = onClickOutside;
  }

  enable() {

    this.appContainer.addEventListener('click', this.handleDocumentClick);
  }

  disable() {

    this.appContainer.removeEventListener('click', this.handleDocumentClick);
  }

  handleDocumentClick = (event) => {

    const area = ReactDOM.findDOMNode(this.component);

    if (!area.contains(event.target)) {
        this.onClickOutside(event)
    }
  }
}

사용 예

import React, {Component} from 'react';
import {APP_CONTAINER} from '../const';
import ClickOutsideBehavior from '../ClickOutsideBehavior';

export default class AddCardControl extends Component {

  constructor() {
    super();

    this.state = {
      toggledOn: false,
      text: ''
    };

    this.clickOutsideStrategy = new ClickOutsideBehavior({
      component: this,
      appContainer: APP_CONTAINER,
      onClickOutside: () => this.toggleState(false)
    });
  }

  componentDidMount () {

    this.setState({toggledOn: !!this.props.toggledOn});
    this.clickOutsideStrategy.enable();
  }

  componentWillUnmount () {
    this.clickOutsideStrategy.disable();
  }

  toggleState(isOn) {

    this.setState({toggledOn: isOn});
  }

  render() {...}
}

메모들

통행증을 보관해 두려고component라이프 사이클 훅을 사용하여 다음과 같은 방법으로 덮어씁니다.

const baseDidMount = component.componentDidMount;

component.componentDidMount = () => {
  this.enable();
  baseDidMount.call(component)
}

component컴포넌트가 컨스트럭터에게 전달됩니다.ClickOutsideBehavior.
이렇게 이 이너블/ 보일러 이 좋아 않습니다.

오래된 질문인 것은 알지만, 계속 이런 질문을 하게 되고, 간단한 형식으로 이해하는 데 많은 어려움을 겪었습니다.만약 이것이 누군가의 삶을 조금 더 편하게 해준다면, Outside Click Handler by Airbnb를 사용해보세요.자체 코드를 작성하지 않고도 이 작업을 수행할 수 있는 가장 간단한 플러그인입니다.

예:

hideresults(){
   this.setState({show:false})
}
render(){
 return(
 <div><div onClick={() => this.setState({show:true})}>SHOW</div> {(this.state.show)? <OutsideClickHandler onOutsideClick={() => 
  {this.hideresults()}} > <div className="insideclick"></div> </OutsideClickHandler> :null}</div>
 )
}

언급URL : https://stackoverflow.com/questions/32553158/detect-click-outside-react-component

반응형