React/React 문법

[React]div 외부 클릭을 감지 Hook으로 구현

DevStory 2021. 11. 2.

이번 포스팅에서는 Hook에서 div 외부 클릭을 감지하는 방법을 소개합니다.

 

이전에 컴포넌트 외부 클릭을 감지하는 방법에 대해 해당 포스팅에서 소개하였지만, 클래스 컴포넌트 위주의 설명이었고 커스텀 훅을 사용하지 않았기 때문에 이번에는 커스텀 훅 기반으로 구현합니다.

 


드롭다운 생성

드롭다운에 매핑되는 객체의 배열을 작성합니다.

const objList = [
  { code: "", name: "", key: "key_empty" },
  { code: "A", name: "A", key: "key_A" },
  { code: "B", name: "B", key: "key_B" },
  { code: "C", name: "C", key: "key_C" }
];

 

code는 option의 value, name은 화면에 표시되는 값, key는 option 엘리먼트를 동적으로 생성하므로 매핑되는 키입니다.

 

다음은 위에서 작성한 배열 데이터와 드롭다운에서 선택한 값을 가지는 state를 작성합니다.

// 드롭다운에 셋팅되는 객체 배열을 가지는 state
const [dropDownList, setDropDownList] = useState([]);

// 드롭다운에서 선택한 값을 가지는 state
const [selectValue, setSelectValue] = useState();

 

useEffect() 두 번째 매개변수로 빈 배열( [] )을 전달하여 컴포넌트가 마운트된 직후 한 번만 실행되는 코드를 작성합니다.

useEffect(() => {
  setDropDownList(objList);
}, []);

API 통신을 한다고 가정하고 state에 배열 데이터를 할당합니다.

 

마지막으로 return문에 map 함수를 이용하여 option 엘리먼트를 동적으로 생성하는 코드를 작성합니다.

return (
  <div>
    <select onChange={(e) => setSelectValue(e.target.value)}>
      {dropDownList.map(function (data) {
        return (
          <option key={data.key} value={data.code}>
            {data.name}
          </option>
        );
      })}
    </select>
  </div>
);

 

다음은 지금까지 컴포넌트를 구현한 코드입니다.

const objList = [
  { code: "", name: "", key: "key_empty" },
  { code: "A", name: "A", key: "key_A" },
  { code: "B", name: "B", key: "key_B" },
  { code: "C", name: "C", key: "key_C" }
];

function App() {
  const [dropDownList, setDropDownList] = useState([]);
  const [selectValue, setSelectValue] = useState();

  useEffect(() => {
    setDropDownList(objList);
  }, []);

  return (
    <div>
      <select onChange={(e) => setSelectValue(e.target.value)}>
        {dropDownList.map(function (data) {
          return (
            <option key={data.key} value={data.code}>
              {data.name}
            </option>
          );
        })}
      </select>
    </div>
  );
}

div 외부 클릭 감지

위에서 드롭다운에 데이터를 화면에 출력하고 선택한 값을 state에 할당하는 과정을 구현하였습니다.

 

이제 div 외부 클릭을 감지하는 코드를 구현해보겠습니다.

 

Hook에서 지원하는 useRef 함수로 ref 객체를 생성합니다.

const outsideRef = useRef(null);

 

ref 객체인 outsideRef와 div 엘리먼트를 연결합니다.

return (
  <div ref={outsideRef}>
    <select onChange={(e) => setSelectValue(e.target.value)}>
      {dropDownList.map(function (data) {
        return (
          <option key={data.key} value={data.code}>
            {data.name}
          </option>
        );
      })}
    </select>
  </div>
);

 

outsideRef 객체에 어떠한 값이 변경되었을 때만 실행되는 useEffect() 함수를 작성합니다.

useEffect(() => {
  function handleClickOutside(event) {
    // 현재 document에서 mousedown 이벤트가 동작하면 호출되는 함수입니다.
    if (outsideRef.current && !outsideRef.current.contains(event.target)) {
      console.log(`div 외부 클릭을 감지!`);
    }
  }
  document.addEventListener("click", handleClickOutside);
  
  return () => {
    document.removeEventListener("click", handleClickOutside);
  };
}, [outsideRef]);

document에서 click 이벤트가 동작하면, handleClickOutside 함수가 실행되도록 이벤트 리스너를 추가합니다.

 

다음은 지금까지 작성한 코드입니다.

const objList = [
  { code: "", name: "", key: "key_empty" },
  { code: "A", name: "A", key: "key_A" },
  { code: "B", name: "B", key: "key_B" },
  { code: "C", name: "C", key: "key_C" }
];

export default function App() {
  const [dropDownList, setDropDownList] = useState([]);
  const [selectValue, setSelectValue] = useState();
  const outsideRef = useRef(null);

  useEffect(() => {
    setDropDownList(objList);
  }, []);

  useEffect(() => {
    // 현재 document에서 mousedown 이벤트가 동작하면 호출되는 함수입니다.
    function handleClickOutside(event) {
      if (outsideRef.current && !outsideRef.current.contains(event.target)) {
        console.log("div 외부 클릭을 감지!");
      }
    }
    document.addEventListener("click", handleClickOutside);
    return () => {
      document.removeEventListener("click", handleClickOutside);
    };
  }, [outsideRef]);

  return (
    <div ref={outsideRef}>
      <select onChange={(e) => setSelectValue(e.target.value)}>
        {dropDownList.map(function (data) {
          return (
            <option key={data.key} value={data.code}>
              {data.name}
            </option>
          );
        })}
      </select>
    </div>
  );
}

커스텀 훅으로 구현

지금까지 div 외부 클릭을 감지하는 코드를 구현하였습니다.

 

하지만, 위에서 구현한 코드는 몇 가지 문제가 있습니다.

  • 코드의 가독성이 좋지 않음
  • 재사용성이 없음

 

이러한 단점을 커스텀 훅을 이용하여 구현해보겠습니다.

 

useOutSideRef라는 함수를 구현합니다.

function useOutSideRef() {
  const ref = useRef(null);

  useEffect(() => {
    function handleClickOutside(event) {
      // 현재 document에서 mousedown 이벤트가 동작하면 호출되는 함수입니다.
      if (ref.current && !ref.current.contains(event.target)) {
        console.log(`div 외부 클릭을 감지!`);
      }
    }
    document.addEventListener("click", handleClickOutside);

    return () => {
      document.removeEventListener("click", handleClickOutside);
    };
  });

  return ref;
}

위 코드는 div 외부 클릭 감지에서 작성한 코드를 조금만 수정하여 useOutSideRef 함수로 구현하였습니다.

 

그리고 App 컴포넌트에서 div 외부 클릭 감지에서 작성한 코드를 제거 후 ref 객체인 outsideRef는 위에서 작성한 useOutSideRef 함수를 이용하여 생성합니다.

const outsideRef = useOutSideRef(null);

 

다음은 지금까지 작성한 코드입니다.

const objList = [
  { code: "", name: "", key: "key_empty" },
  { code: "A", name: "A", key: "key_A" },
  { code: "B", name: "B", key: "key_B" },
  { code: "C", name: "C", key: "key_C" }
];

function useOutSideRef() {
  const ref = useRef(null);

  useEffect(() => {
    function handleClickOutside(event) {
      // 현재 document에서 mousedown 이벤트가 동작하면 호출되는 함수입니다.
      if (ref.current && !ref.current.contains(event.target)) {
        console.log(`div 외부 클릭을 감지!`);
      }
    }
    document.addEventListener("click", handleClickOutside);

    return () => {
      document.removeEventListener("click", handleClickOutside);
    };
  });

  return ref;
}

export default function App() {
  const [dropDownList, setDropDownList] = useState([]);
  const [selectValue, setSelectValue] = useState();
  const outsideRef = useOutSideRef(null);

  useEffect(() => {
    setDropDownList(objList);
  }, []);

  return (
    <div ref={outsideRef}>
      <select onChange={(e) => setSelectValue(e.target.value)}>
        {dropDownList.map(function (data) {
          return (
            <option key={data.key} value={data.code}>
              {data.name}
            </option>
          );
        })}
      </select>
    </div>
  );
}
반응형

댓글