You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React实现双击屏蔽单击:带参数场景的自定义Hook复用问题

解决带参数的单击/双击区分Hook复用问题

我需要实现一个定时器逻辑区分单击和双击,确保双击时不触发单击。现有自定义Hook useNoClickOnDblClick在无参数场景下正常工作,但遍历渲染MineCard卡片时,需要给每个卡片的单击/双击事件传递对应的level数据,直接把数据传入Hook会影响复用性,该怎么处理?


现有Hook代码

export const useNoClickOnDblClick = (onClick: () => void, onDoubleClick: () => void) => {
  const [clicks, setClicks] = useState(0);

  useEffect(() => {
    let singleClickTimer: string | number | NodeJS.Timeout | undefined;
    if (clicks === 1) {
      singleClickTimer = setTimeout(function () {
        onClick();
        setClicks(0);
      }, 250);
    } else if (clicks === 2) {
      onDoubleClick();
      setClicks(0);
    }
    return () => clearTimeout(singleClickTimer);
  }, [clicks]);

  return {
    clicks,
    setClicks
  };
};

基础无参数使用示例

const { setClicks } = useNoClickOnDblClick(handleRenderInfo, () => console.log('dbl click'));

<SummaryCard
  data={data}
  onCardClick={() => setClicks((prev) => prev + 1)}
/>

待解决的传参场景

const { setClicks } = useNoClickOnDblClick(handleRenderLevelInfo, () => console.log('dbl click'));

{mineData.levelArr.map((level, i) => {
        return (
          <MineCard
            key={i}
            data={level}
            onCardClick={() => setClicks((prev) => prev + 1)}
            //onCardClick={() => handleRenderLevelInfo({ ...level, idx: i })}
            onCardDblClick={() =>
              navigateTo({ path: `/${surveyPointsPrefix}/${mineID}/${level.level}` })
            }
          />
        );
      })}

解决方案

1. 调整Hook,适配带参数的回调并封装事件逻辑

修改Hook,用泛型支持任意参数类型,同时返回统一的点击处理函数,既保留复用性,又能传递自定义参数:

export const useNoClickOnDblClick = <T>(
  onClick: (param: T) => void,
  onDoubleClick: (param: T) => void
) => {
  const [clicks, setClicks] = useState(0);
  const [clickParam, setClickParam] = useState<T | null>(null);

  useEffect(() => {
    let singleClickTimer: NodeJS.Timeout | undefined;
    if (clicks === 1 && clickParam !== null) {
      singleClickTimer = setTimeout(() => {
        onClick(clickParam);
        setClicks(0);
        setClickParam(null);
      }, 250);
    } else if (clicks === 2 && clickParam !== null) {
      onDoubleClick(clickParam);
      setClicks(0);
      setClickParam(null);
    }
    return () => clearTimeout(singleClickTimer);
  }, [clicks, clickParam, onClick, onDoubleClick]);

  const handleClick = (param: T) => {
    setClickParam(param);
    setClicks(prev => prev + 1);
  };

  return { handleClick };
};

2. 给每个卡片分配独立的Hook实例

原来的写法会让所有卡片共用同一个clicks状态,导致点击不同卡片互相干扰,必须让每个卡片拥有独立的Hook状态,最规范的做法是封装子组件:

封装带Hook的子组件

const LevelCard = ({ level, idx, mineID, surveyPointsPrefix, handleRenderLevelInfo }) => {
  const { handleClick } = useNoClickOnDblClick(
    (param) => handleRenderLevelInfo(param),
    (param) => navigateTo({ path: `/${surveyPointsPrefix}/${mineID}/${param.level}` })
  );

  return (
    <MineCard
      key={idx}
      data={level}
      onCardClick={() => handleClick({ ...level, idx })}
    />
  );
};

父组件中遍历使用

{mineData.levelArr.map((level, i) => (
  <LevelCard
    key={i}
    level={level}
    idx={i}
    mineID={mineID}
    surveyPointsPrefix={surveyPointsPrefix}
    handleRenderLevelInfo={handleRenderLevelInfo}
  />
))}

3. 核心要点

  • 独立状态隔离:每个卡片必须拥有自己的Hook实例,避免状态互相干扰。
  • 泛型兼容任意参数:Hook通过泛型<T>支持不同结构的参数,不影响在其他场景的复用。
  • 简化外部调用:外部只需传入参数并调用handleClick,无需关心内部定时器逻辑。

内容的提问来源于stack exchange,提问作者Tom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 07:15:06