自建轮子分享:react-khooks 一个基于React键盘事件的自定义hooks

react-khooks

这是一个基于React的键盘事件的自定义hooks,是基于公司的现有的业务需求封装的,支持一个按键绑定多个事件(事件队列管理),只触发最新绑定的事件,队列顺序根据业务去维护,毕竟正常来说一个按键在某一时刻只会触发单一功能事件。(其实可以通过事件权重去托管给emitter本身去管理)

ps:其中关于内嵌队列的设计原本想的更复杂,但实际业务没有没那复杂,就没有做更深层次的扩展。在此只是分享记录,因为没有参考任何轮子,所以可能还有很多想不到啦,如需可自取,链接在结尾

📦 Install

bash 复制代码
$ npm i react-khooks --save

🔨 Usage

jsx 复制代码
// 根据需要结构以下对应模块使用
import { useKeyEvent } from 'react-khooks'; //键盘hooks
import { emitter } from 'react-khooks'; //事件队列中心(单例)
参数 说明
keyName 键盘按键名key或keyCode,建议统一,必传(组合键使用+连接,如:ctrl+按键名,请注意和浏览器以及系统按键的冲突问题)
callback 回调函数,默认参数e为KeyboardEvent事件对象,必传
toolEventName 自定义事件名称,作为该键盘事件队列中的唯一标识,必传
type 键盘弹起或按下(keyup/keydown),默认keyup
delayTime 防抖/节流延迟时间,默认0为不使用节流/防抖 (如果使用keydown,建议设置)
delayType 1节流/2防抖,默认1
  • 一般来说,你只需要传入三个属性:keyName(键盘按键名),callback(回调函数),toolEventName(自定义事件名)
  • 支持复合键+按键和**复合键+复合键+按键的组合按键事件 (复合键是指ctrl/alt/shift)
  • 你可以基于callback的默认参数KeyboardEvent根据该事件对象进行更多逻辑控制或完成更多复合按键
  • useKeyEvent默认返回事件队列实例,这是一个单例,意味着你在任何地方返回的都是同一个emitter实例或者直接导入,以手动控制事件队列

基本用法:

jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const handleClick = () => {
    alert('按键z触发');
  };

  useKeyEvent({ keyName: 'z', callback: handleClick, toolEventName: 'alert' });

  return (
    <div>
      <div>按键z键弹出提示</div>
    </div>
  );
};

组合键(ctrl/alt/shift+按键):

  • 回调接收默认参数(KeyboardEvent 事件对象)
jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const [num, setNum] = useState(0);
  const handleClick = (e) => {
    setNum(num + 1);
  };
  useKeyEvent({ keyName: `alt+v`, callback: handleClick, toolEventName: '增加num' });
  useKeyEvent({ keyName: `ctrl+v`, callback: handleClick, toolEventName: '增加num' });
  useKeyEvent({ keyName: `shift+v`, callback: handleClick, toolEventName: '增加num' });

  return (
    <div>
      <div>按键ctrl/alt/shift+v增加num</div>
      <span>num: {num}</span>
    </div>
  );
};

动态切换绑定热键:

jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const [num, setNum] = useState(0);
  const [hotKey, setHotKey] = useState('m');
  const handleClick = () => {
    setNum(num + 1);
  };

  useKeyEvent({ keyName: hotKey, callback: handleClick, toolEventName: '修改num' });

  return (
    <div>
      <div>按键{hotKey}键修改num</div>
      <div>num: {num}</div>
      <div>
        <button onClick={() => setHotKey('n')}>切换为使用n键修改num</button>
      </div>
    </div>
  );
};

节流/防抖

jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const [num, setNum] = useState(0);
  const handleClick = useCallback(() => {
    setNum(num + 1);
  }, [num]);
  useKeyEvent({
    keyName: 'q',
    callback: handleClick,
    toolEventName: '长按q键修改num',
    delayTime: 500,
    type: 'keydown',
    delayType: 1,
  });
  useKeyEvent({
    keyName: 'w',
    callback: handleClick,
    toolEventName: '长按w键修改num',
    delayTime: 500,
    type: 'keydown',
    delayType: 2,
  });

  return (
    <div>
      <div>节流:长按q键修改num(每500ms触发一次)</div>
      <div>num: {num}</div>
      <div>防抖:长按w键修改num(直到抬起触发一次)</div>
    </div>
  );
};

关于回调函数 callback 的处理强烈建议使用 useCallback,避免组件重新渲染时频繁订阅取消

  • 使用 useCalback 内 setState 获取当前状态
jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const [num, setNum] = useState(0);

  const handleClick = useCallback(() => {
    setNum((num) => num + 1);
  }, []);

  useKeyEvent({ keyName: 'a', callback: handleClick, toolEventName: 'add' });

  return (
    <div>
      设置快捷键 a 修改 useState 定义的 num 数据
      <div>num: {num}</div>
    </div>
  );
};
  • useCallback 依赖获取当前状态
jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const [num, setNum] = useState(0);

  const handleClick = useCallback(() => {
    setNum(num - 1);
  }, [num]);

  useKeyEvent({ keyName: 's', callback: handleClick, toolEventName: 'reduce' });

  return (
    <div>
      设置快捷键 s 修改 useState 定义的 num 数据
      <div>num: {num}</div>
    </div>
  );
};
  • 不推荐(会导致组件重新渲染时频繁取消/订阅,性能差,虽然内部做了处理,避免这个问题,但是还是不推荐)
  • 目前做了优化,组件重新渲染时不会频繁取消/订阅(虽然好像可以在回调中拿到最新的state,但还是推荐使用前门两种方式获取state)
jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const [num, setNum] = useState(0);
  const handleClick = (param) => {
    setNum(num + param);
  };

  useKeyEvent({ keyName: 'ctrl+x', callback: () => handleClick(1), toolEventName: 'add_2' });
  useKeyEvent({ keyName: 'shift+x', callback: () => handleClick(-1), toolEventName: 'reduce_2' });

  return (
    <div>
      <div>按键ctrl+x增加num</div>
      <div>按键shift+x减小num</div>
      <button onClick={() => handleClick(1)}> 加 1 </button>
      <span>num: {num}</span>
      <button onClick={() => handleClick(-1)}> 减 1 </button>
    </div>
  );
};

freezeAll/unfreezeAll:冻结/解冻所有键盘事件队列

jsx 复制代码
import React, { useState, useCallback } from 'react';
import { useKeyEvent } from 'react-khooks';

export default () => {
  const [num, setNum] = useState(0);

  const handleClick = () => {
    setNum((num) => num + 1);
  };

  const { emitter } = useKeyEvent({ keyName: 'f', callback: handleClick, toolEventName: 'add' });
  // 你也可以直接 import { emitter } from 'react-khooks',因为这里的emitter始终是同一个实例

  return (
    <div>
      <p>按下键盘f键修改num</p>
      <span style={{ border: '1px solid #ccc' }} onClick={() => emitter.freezeAll()}>
        冻 结
      </span>
      <div>num: {num}</div>
      <span style={{ border: '1px solid #ccc' }} onClick={() => emitter.unfreezeAll()}>
        解 冻
      </span>
    </div>
  );
};

因为已经满足当前业务需求,所以没有在做更多功能的实现,如需自取,附上地址:

npm地址

git仓库

相关推荐
小林ixn3 小时前
从零到一理解 React 父子组件通信:手写一个 Todo 应用带你彻底搞懂单向数据流
前端·javascript·react.js
qetfw6 小时前
MXU:Tauri 2 + React 的 MaaFramework 跨平台 GUI 源码
前端·python·react.js·前端框架·开源项目·效率工具
鱼毓屿御7 小时前
从「只会聊」到「边想边做」的跃迁
前端·学习·react.js
嘟嘟07178 小时前
搞懂 useState 从原生 DOM 到 React 惰性初始化的完整链路
前端·react.js·编程语言
先吃饱再说11 小时前
React 组件设计:从 Props 传递到组件封装
前端·react.js
小月土星13 小时前
React 状态管理核心原理:从批量 DOM 到派生状态
react.js
烬羽15 小时前
AI 写代码总翻车?试试"先画图再砌墙"的 Vibe Coding 三步法
react.js·ai编程·vibecoding
光影少年15 小时前
react navite 页面跳转、传参、路由监听、导航栏自定义
前端·react native·react.js
LaughingZhu17 小时前
Product Hunt 每日热榜 | 2026-07-23
前端·神经网络·react.js·搜索引擎·前端框架
不好听61317 小时前
Fragment:React 的 `<></>` 和原生的 DocumentFragment,同名不同命
前端·react.js·dom