useElementClick 给ReactNode增加点击事件

57 阅读1分钟

当组件中需要给外部传入的单个ReactNode节点添加点击事件,以便简洁组件外代码。可以使用 cloneElement 进行绑定一个默认点击事件。

import React, { type ReactNode, useRef } from 'react';

/**
 * 自定义Hook,用于处理元素点击事件,防止重复点击
 * @param triggerNode 触发点击的React节点
 * @param onClick 点击事件回调函数
 * @param stopPropagation 是否阻止事件冒泡,默认为true
 * @param debounceMs 防重复点击的时间间隔,默认为300ms
 * @returns 克隆后的React元素,带有防重复点击的onClick处理函数
 */
const useNodeClick = (
  triggerNode?: ReactNode,
  onClick?: (event: React.MouseEvent) => void,
  stopPropagation = true,
  debounceMs = 300
) => {
  // 检查是否为单个React元素
  const isOnly = React.isValidElement(triggerNode)
  // 使用ref记录上次点击的时间戳
  const lastClickTime = useRef(0)

  // 如果不是单个元素,给出警告
  if (!isOnly) {
    console.warn('请使用单元素,单元素默认click事件不冒泡', triggerNode)
  }

  // 如果不是单个元素,直接返回原始节点
  if (!isOnly) {
    return triggerNode
  }

  // 克隆元素并添加防重复点击的onClick处理函数
  return React.cloneElement(triggerNode as React.ReactElement<any>, {
    onClick: (event: React.MouseEvent) => {
      // 获取当前时间戳
      const now = Date.now()
      // 如果距离上次点击时间小于设定的间隔时间,则阻止点击
      if (now - lastClickTime.current < debounceMs) return

      // 更新上次点击时间为当前时间
      lastClickTime.current = now

      // 根据参数决定是否阻止事件冒泡
      if (stopPropagation) {
        event.stopPropagation();
      }
      // 执行传入的点击回调函数
      if (onClick) {
        onClick(event);
      }
      // 执行子元素原有的onClick事件
      const child = triggerNode as React.ReactElement<any>;
      if (child.props.onClick) {
        child.props.onClick(event);
      }
    },
  })
}

export default useNodeClick