关于react.js:React自定义hook之useClickOutside判断是否点击DOM之外区域

5次阅读

共计 3066 个字符,预计需要花费 8 分钟才能阅读完成。

最近在开发业务需要的时候,有一个场景是点击弹窗之外的区域后,执行某些操作。比方咱们罕用的 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!

正文完
 0