Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 98x 98x 98x 98x 98x 70x 98x 98x 98x 70x 70x 70x 70x 70x 70x 70x 98x | import { useRef, useEffect } from "react"; /** * 提供一个 DOM,在 DOM 的外部点击之后,会触发回调函数 * @param domRefs 给定一个或多个 DOM 的 ref,在此 DOM 之外点击认为是外部点击 */ export function useOutsideClick( domRefs: React.RefObject<HTMLElement> | React.RefObject<HTMLElement>[], enabled = true ) { const timerRef = useRef(null); const eventRef = useRef(null); const reactEvtRef = useRef(null); const listenerRef = useRef(null); /** * 清理外部点击事件处理器 */ const remove = () => { listenerRef.current = null; }; /** * 注册外部点击事件处理器 * @param handle DOM 外部点击时的回调函数 */ const listen = (handle: (evt: MouseEvent) => void) => { listenerRef.current = (evt: MouseEvent) => { const refs = !Array.isArray(domRefs) ? [domRefs] : domRefs; for (const domRef of refs) { if (domRef.current && domRef.current.contains(evt.target as Node)) { return; } } // Portal 上模拟的冒泡事件比 DOM 事件晚 timerRef.current = setTimeout(() => { if (reactEvtRef.current !== evt) { handle(evt); } }, 0); }; }; // 在 unmount 之后,无论如何要清理 useEffect(() => { Iif (!enabled) { return () => null; } eventRef.current = evt => { if (listenerRef.current) { clearTimeout(timerRef.current); listenerRef.current(evt); } }; document.addEventListener("mousedown", eventRef.current); return () => { remove(); clearTimeout(timerRef.current); document.removeEventListener("mousedown", eventRef.current); }; }, [enabled]); return { listen, remove, // 需要忽略的组件要传入的 Props ignoreProps: { onMouseDown: (evt: React.MouseEvent) => { reactEvtRef.current = evt.nativeEvent; return evt; }, }, }; } |