实现一个 useOnClickOutside hook

231 阅读1分钟
import React, { useEffect, useRef } from 'react'

function useOnClickOutside<T extends HTMLElement>(
  onClick: (event: MouseEvent) => void,
  deps?: React.DependencyList
): React.RefObject<T> {
  const nodeRef = useRef<T>(null)

  const handleClickAnywhere = (event: MouseEvent) => {
    // 如果点击的元素不在目标元素内,处理回调 onClick
    if (nodeRef.current && !nodeRef.current.contains(event.target as Node)) {
      onClick(event)
    }
  }

  // 监听文档中的任何点击事件
  useEffect(() => {
    document.addEventListener('mousedown', handleClickAnywhere)
    return () => document.removeEventListener('mousedown', handleClickAnywhere)
  }, deps || [])

  return nodeRef
}
  
export default useOnClickOutside;

🤔 为什么需要传 deps?
为了避免闭包陷阱。useOnClickOutside 的 onClick 内如果有用到其他变量,而 useEffect 的回调函数形成了闭包,变量总是初始值(比如下面例子中 contentCompleted 总是 false)。

使用:

const Demo: React.FC<{ contentCompleted: boolean }> = ({ contentCompleted }) => {
  const containerRef = useOnClickOutside<HTMLDivElement>(() => {
    if (contentCompleted) {
      onClose()
    }
  }, [contentCompleted])
  
  return <div ref={containerRef}>...</div>
}