最近在开发业务需要的时候,有一个场景是点击弹窗之外的区域后,执行某些操作。比方咱们罕用的github左上角的搜寻框,当点击了搜寻框之外的区域当前,搜寻框就会主动勾销搜寻并收缩起来。

通过调研发现应用useRef+浏览器事件绑定能够实现这一需要,并且能够将这一性能形象为自定义hook。

本文将首先介绍如何用传统形式实现这一需要,而后介绍如何形象成自定义hook,最初联合typescript类型,欠缺这一自定义hook。

实现检测点击对象外区域

import React, { useEffect, useRef } from "react";const Demo: React.FC = () => {  // 应用useRef绑定DOM对象  const domRef = useRef<HTMLDivElement>(null);  // 组件初始化绑定点击事件  useEffect(() => {    const handleClickOutSide = (e: MouseEvent) => {      // 判断用户点击的对象是否在DOM节点外部      if (domRef.current?.contains(e.target as Node)) {        console.log("点击了DOM外面区域");        return;      }      console.log("点击DOM里面区域");    };    document.addEventListener("mousedown", handleClickOutSide);    return () => {      document.removeEventListener("mousedown", handleClickOutSide);    };  }, []);  return (    <div      ref={domRef}      style={{        height: 300,        width: 300,        background: "#bfa",      }}    ></div>  );};export default Demo;

代码不难理解,首先咱们在函数式组件外面写了一个长度和宽度都是300像素的正方形,而后创立了一个名为domRef的对象将其绑定到dom节点上,最初在useEffect钩子外面申明handleClickOutSide的办法判断用户是否点击了指定的DOM区域,并应用document.addEventListener办法增加事件监听,组件卸载时清理事件监听。
在实现的过程中,最外围的是利用了Ref对象上的contains办法,通过钻研发现,Node.contains办法是浏览器的原生办法,其次要的作用是判断传入的DOM节点是否为该节点的后辈节点。

应用根本形式实现后,接着封装自定义hook。

封装useClickOutside hook

大家在了解自定义hook时不用心生畏惧,无非就是调用了其余hook的一般函数,上面来看代码实现:

import { RefObject, useEffect } from "react";const useClickOutside = (ref: RefObject<HTMLElement>, handler: Function) => {  useEffect(() => {    const listener = (event: MouseEvent) => {      if (!ref.current || ref.current.contains(event.target as HTMLElement)) {        return;      }      handler(event);    };    document.addEventListener("click", listener);    return () => {      document.removeEventListener("click", listener);    };  }, [ref, handler]);};export default useClickOutside;

能够看到,代码将判断是否点击了DOM之外区域的逻辑都抽离进去,这样在应用时,只须要把DOM节点传递给useClickOutside即可,自定义hook的第二个参数接管一个回调函数,能够在回调函数外面做各种事件。来看一下应用自定义hook后的代码:

import React, { useRef } from "react";import useClickOutside from "../hooks/useOnClickOutside";const Demo: React.FC = () => {  // 应用useRef绑定DOM对象  const domRef = useRef<HTMLDivElement>(null);  useClickOutside(domRef, () => {    console.log("点击了内部区域");  });  return (    <div      ref={domRef}      style={{        height: 300,        width: 300,        background: "#bfa",      }}    ></div>  );};export default Demo;

能够看到,组件的代码失去大大的简化,而且抽离进去的自定义hook能够在其余组件中复用。但到这一步有优化的空间吗?当然有的,那就是在类型定义方面,能够应用泛型进行优化。
在方才编写的useClickOutside自定义hook中,对ref对象的定义是:RefObject<HTMLElement>,这种定义其实是不合理的,因为HTMLElement不够具体,有可能传入的是HTMLDivElement或者是HTMLAnchorElement,也有可能是HTMLSpanElement,这里咱们能够应用泛型对其加以限度。

应用泛型优化类型定义

import { RefObject, useEffect, useRef } from 'react'export function useOnClickOutside<T extends HTMLAnchorElement>(  node: RefObject<T | undefined>,  handler: undefined | (() => void)) {  const handlerRef = useRef<undefined | (() => void)>(handler)  useEffect(() => {    handlerRef.current = handler  }, [handler])  useEffect(() => {    const handleClickOutside = (e: MouseEvent) => {      if (node.current?.contains(e.target as Node) ?? false) {        return      }      if (handlerRef.current) handlerRef.current()    }    document.addEventListener('mousedown', handleClickOutside)    return () => {      document.removeEventListener('mousedown', handleClickOutside)    }  }, [node])}

优化后的hook里,应用泛型T指代传入的类型继承自HTMLElement,这样在申明ref对象的时候就能够用T指代传入的DOM类型,应用泛型能够增强对传入参数的束缚,大家在我的项目开发中能够多加尝试。
在handleClickOutside办法中,应用了双问号??判断,双问号的意思是如果后面的局部为undefined则返回前面的内容,也就是false。
本文最初完整版的useClickOutside hook借鉴了uniswap开源我的项目
我的项目地址

谢谢浏览,如果感觉不错,欢送点赞o( ̄▽ ̄)d!