React/React 문법

[React]컴포넌트 외부 클릭을 감지

DevStory 2021. 7. 28.

StackOverFlow를 눈팅하던 도중 2017년에 작성된 흥미로운 글을 발견했습니다.

컴포넌트 외부 클릭을 어떻게 감지하는지 질문하는 글이었습니다.

 

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

 

Detect click outside React component

I'm looking for a way to detect if a click event happened outside of a component, as described in this article. jQuery closest() is used to see if the target from a click event has the dom element ...

stackoverflow.com

위 질문의 답변과 구글링을 해보니 대부분 React에서 지원하는 ref와 JavaScript의 EventListener를 사용하여 외부 클릭을 감지하였습니다. 이번 포스팅에서는 컴포넌트의 외부 클릭을 감지하는 방법 및 코드를 정리합니다.

붉은색 테두리 외부를 클릭하면, select의 외부 클릭을 감지! 라는 문구가 console에 출력되는 코드를 작성했습니다.


mousedown와 click 차이

mousedown

- html element를 클릭하는 순간 이벤트가 동작합니다.

 

click

- html element를 클릭이 끝나는 순간 이벤트가 동작합니다.

- html element를 클릭하고 있는 상태에서 html element를 벗어나고 클릭을 떼는 경우 click 이벤트는 동작하지 않습니다.


Class기반의 컴포넌트로 구현

Ref는 React에서 Html Element에 접근할 수 있게 해주는 기능입니다.

자세한 내용은 공식 문서를 참고해주세요.

 

constructor

생성자에서 React.createRef()를 사용하여 outsideRef라는 ref를 생성합니다.

App 컴포넌트와 이벤트 핸들러 함수 handleClickOutside를 bind 합니다.

constructor(props) {
  super(props);

  // select element를 참조하기 위해 ref를 생성합니다.
  this.outsideRef = React.createRef();
  this.handleClickOutside = this.handleClickOutside.bind(this);
}

 

componentDidMount

App 컴포넌트가 마운트 되면, document에 이벤트 리스너를 추가합니다.

현재 활성화되어 있는 브라우저의 탭의 document에서 mousedown이벤트가 동작하면, handleClickOutside 함수가 실행됩니다.

※ document에 대해 모르신다면, 여기를 클릭해주세요.

componentDidMount() {
  console.log("componentDidMount()");
  // 컴포넌트가 마운트되면, document에 이벤트리스너를 추가합니다.
  // 현재 document에서 'mousedown'이벤트가 동작하면 handleClickOutside 함수가 실행됩니다.
  document.addEventListener("mousedown", this.handleClickOutside);
}

 

componentWillunmount

App 컴포넌트가 마운트 해제되면, document에서 이벤트 리스너를 제거합니다.

현재 활성화되어 있는 브라우저의 탭의 document에서 mousedown이벤트가 동작하면, handleClickOutside 함수가 더 이상 실행되지 않습니다.

componentWillUnmount() {
  console.log("componentWillUnmount()");
  // 컴포넌트가 마운트해제되면, document에 이벤트리스너를 제거합니다.
  // 더 이상'mousedown'이벤트가 동작하더라도 handleClickOutside 함수가 실행되지 않습니다.
  document.removeEventListener("mousedown", this.handleClickOutside);
}

 

handleClickOutside

이벤트 핸들러 함수 handleClickOutside를 작성합니다.

this.outsideRef는 생성자에서 outsideRef를 생성했다면 true, 생성하지 않았다면 false입니다.

this.outsideRef.current.contains(event.target)는 현재 이벤트를 실행한 element가 this.outsideRef.current에 포함이 되지 않으면 false, 포함되거나 동일하다면 true입니다.

외부를 클릭했을 경우 동작해야 하므로 this.outsideRef.current.contains(event.target) 앞에 !를 붙입니다.

handleClickOutside(event) {
  console.log(`handleClickOutside()`);
  console.log(this.outsideRef.current);
  console.log(event.target);
  // 1. 생성자에서 outsideRef를 생성했거나
  // 2. outsideRef의 html element에 이벤트가 발생한 html element가 포함되어 있지 않을 경우
  if (this.outsideRef && !this.outsideRef.current.contains(event.target)) {
    console.log(`select의 외부 클릭을 감지!`);
  }
}

 

작성한 코드의 구조

붉은색 테두리 내부를 클릭하면, event.target이 붉은색 테두리의 내부 html Element입니다.

→ event target이 select Element에 포함되거나 동일합니다.

→ this.outsideRef.current.contains(event.target)는 true입니다.

 

붉은색 테두리 외부를 클릭하면, event.target이 붉은색 테두리의 외부 html Element입니다.

→ event target이 select Element에 포함되지 않습니다.

→ this.outsideRef.current.contains(event.target)는 false입니다.

 

아래 Console.log 명령어는 붉은색 테두리 내부와 외부를 클릭했을 경우 출력되는 값이 다릅니다.

console.log(this.outsideRef.current);
console.log(event.target);

붉은색 테두리 내부를 클릭

 

붉은색 테두리 외부를 클릭

render

생성자에서 생성한 outsideRef와 select Element를 연결합니다.

outsideRef를 사용하여 select Element를 참조할 수 있습니다.

render() {
    return (
      <div>
        <form>
          {/*
          select Element를 생성자(constructor)에서 생성한 outsideRef와 연결합니다.
          */}
          <select className="App" name="color" ref={this.outsideRef}>
          ...
          </select>
        </form>
      </div>
    );
  }

Hook기반의 컴포넌트로 구현

Hook기반의 컴포넌트는 Class기반의 컴포넌트에서 ref를 사용하는 방법과 다릅니다.

Hook기반의 컴포넌트에서 ref를 사용하는 방법은 공식 문서를 참고해주세요.

 

Hook기반의 컴포넌트로 구현한 코드의 설명은 생략합니다.

Class기반의 컴포넌트로 구현한 코드를 이해하셨고, Hook에 대해 공부를 하셨다면, 충분이 이해하실 거라 믿고 있습니다!

반응형

댓글