这个也比较有意思,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}
/>
)}