useOnClickOutside

107 阅读1分钟

这个也比较有意思,uniswap 通过useRef Hook 实现在用户点击了指定 DOM 节点外部时执行一个回调函数。 比如他的这个nav栏的 searchbar 点击外部关闭 SearchBarDropdown 本质是dom 引用和 事件监听

import { RefObject, useEffect, useRef } from 'react'
// 
export function useOnClickOutside<T extends HTMLElement>(
   node:RefObject<T | undefined>,// 指定的Dom节点
   handler:undefined | ()=> void,// 控制函数
   ignoredNodes:Array<RefObject<T | undefined>> = [] // 忽略节点
 ){
  // 这里选择使用ref 1.是因为防止闭包陷阱,保证依赖的handler都是最新的  2. 是性能优化
  const handlerRef = useRef<undefined | () => void>(handler)
  
  useEffect(()=>{
    handlerRef.current = handler
  },[handler])
  
  useEffect(()=>{
     const handleClickOutside = (e:MouseEvent)=>{
        const nodeClicked = node.current?.contains(e.target as Node)
        const ignoredNodeClicked = ignoredNodes.reducer(
          (reducer,val)=> reducer || !!val.current?.contains(e.target as Node) ,
          false)
        // 这里因为nodeClicked: boolean | undefined  会有undefined 所以用 ??
        if((nodeClicked || ignoredNodeClicked) ?? false) return;
         
        if(handlerRef.current) handlerRef.current()
     }
     
     document.addEventListener("mouseDown",handleClickOutside)
     return () => {
      document.removeEventListener('mousedown', handleClickOutside)
    }
  },[node, ignoredNodes])

使用就简单了

 // 控制组件开关的变量isOpen 和 toggleOpen 函数
  const [isOpen, toggleOpen] = useReducer((state: boolean) => !state, false);
 // 设置指定的DOM
  const searchRef = useRef<HTMLDivElement>(null);
  
  <div ref={searchRef}>
    <input/>
  </div>
 在组件中 直接使用Hook 执行监听 
  useOnClickOutside(searchRef, () => {
    isOpen && toggleOpen();
  });
  使用
  {isOpen && (
            <SearchBarDropdown
              toggleOpen={toggleOpen}
              tokens={reducedTokens}
              collections={reducedCollections}
              queryText={debouncedSearchValue}
              hasInput={debouncedSearchValue.length > 0}
              isLoading={tokensAreLoading || collectionsAreLoading}
            />
          )}