React - custom hook - useHotKeys

612 阅读2分钟

描述:

react 自定义hook,实现浏览器监听特定组合按键,比如 ctrl+ m, ctrl + k + j + a + b,0依赖。

点击在线预览: codesandbox

image.png

使用:

// 自定义按键
useHotKey('ctrl+shift+m', () => {
  /*happy Coding*/
})
// 无视空格
useHotKey('ctrl + k', () => {
  /*happy Coding 2*/
})
​
// 自定义keycode映射
const HotkeyDict: { [key: string]: number } = {
  "backspace": 8,
  "tab": 9,
  "enter": 13,
  "shift": 16,
  "ctrl": 17,
  "alt": 18,
  "command": 91,
  "caps-lock": 20,
  "escape": 27,
  "space": 32,
  "page-down": 34,
  "end": 35,
  "arrow-left": 37,
  "arrow-up": 38,
  "arrow-right": 39,
  "arrow-down": 40,
  "insert": 45,
  "delete": 46,
  "0": 48,
  "1": 49,
  "2": 50,
  "3": 51,
  "4": 52,
  "5": 53,
  "6": 54,
  "7": 55,
  "8": 56,
  "9": 57,
  "numpad-0": 96,
  "numpad-1": 97,
  "numpad-2": 98,
  "numpad-3": 99,
  "numpad-4": 100,
  "numpad-5": 101,
  "numpad-6": 102,
  "numpad-7": 103,
  "numpad-8": 104,
  "numpad-9": 105,
  "a": 65,
  "b": 66,
  "c": 67,
  "d": 68,
  "e": 69,
  "f": 70,
  "g": 71,
  "h": 72,
  "i": 73,
  "j": 74,
  "k": 75,
  "l": 76,
  "m": 77,
  "n": 78,
  "o": 79,
  "p": 80,
  "q": 81,
  "r": 82,
  "s": 83,
  "t": 84,
  "u": 85,
  "v": 86,
  "w": 87,
  "x": 88,
  "y": 89,
  "z": 90
}
// 自定义按键映射
useHotKey(
  'ctrl + k',
  () => {},
  hotKeyDict
)

源码 - ts版本:

import { useEffect, useMemo } from "react";
​
const HotkeyDict: { [key: string]: number } = {
  "backspace": 8,
  "tab": 9,
  "enter": 13,
  "shift": 16,
  "ctrl": 17,
  "alt": 18,
  "command": 91,
  "caps-lock": 20,
  "escape": 27,
  "space": 32,
  "page-down": 34,
  "end": 35,
  "arrow-left": 37,
  "arrow-up": 38,
  "arrow-right": 39,
  "arrow-down": 40,
  "insert": 45,
  "delete": 46,
  "0": 48,
  "1": 49,
  "2": 50,
  "3": 51,
  "4": 52,
  "5": 53,
  "6": 54,
  "7": 55,
  "8": 56,
  "9": 57,
  "numpad-0": 96,
  "numpad-1": 97,
  "numpad-2": 98,
  "numpad-3": 99,
  "numpad-4": 100,
  "numpad-5": 101,
  "numpad-6": 102,
  "numpad-7": 103,
  "numpad-8": 104,
  "numpad-9": 105,
  "a": 65,
  "b": 66,
  "c": 67,
  "d": 68,
  "e": 69,
  "f": 70,
  "g": 71,
  "h": 72,
  "i": 73,
  "j": 74,
  "k": 75,
  "l": 76,
  "m": 77,
  "n": 78,
  "o": 79,
  "p": 80,
  "q": 81,
  "r": 82,
  "s": 83,
  "t": 84,
  "u": 85,
  "v": 86,
  "w": 87,
  "x": 88,
  "y": 89,
  "z": 90
}
​
class KeyManager {
  queue: Set<number>
  target: number[]
  constructor(combineAlias: string, hotKeyDict: { [key: string]: number } = HotkeyDict) {
    this.queue = new Set()
    this.target = KeyManager.extractKeyAlias(combineAlias, hotKeyDict);
  }
  get isValid(): boolean {
    return this.target.every(targetKeyCode => this.queue.has(targetKeyCode)) && this.queue.size === this.target.length;
  }
  public add(keyCode: number) {
    this.queue.add(keyCode)
  }
  public remove(keyCode: number) {
    this.queue.delete(keyCode)
  }
  public reset() {
    this.queue.clear();
  }
​
  static extractKeyAlias(combineAlias: string, hotKeyDict: { [key: string]: number }) {
    return combineAlias.replace(/\s/g, '').split('+').map(keyAlias => {
      if (isNaN(hotKeyDict[keyAlias])) {
        throw new Error('nonsupport key alias')
      }
      return hotKeyDict[keyAlias]
    });
  }
}
/**
 * 
 * @param combineAlias key + key + key + ... + key
 * @param callback 回调函数
 */
export function useHotKey(combineAlias: string, callback: () => void): KeyManager {
  const keyManager = useMemo(() => {
    return new KeyManager(combineAlias);
  }, [combineAlias]);
  useEffect(() => {
    document.addEventListener("keydown", handleKeyDown)
    document.addEventListener("keyup", handleKeyUp)
    return () => {
      document.removeEventListener("keydown", handleKeyDown)
      document.removeEventListener("keyup", handleKeyUp)
    }
    function handleKeyDown(e: KeyboardEvent) {
      keyManager.add(e.keyCode);
      if (keyManager.isValid) {
        callback?.();
      }
    }
    function handleKeyUp(e: KeyboardEvent) {
      keyManager.remove(e.keyCode);
    }
  }, [keyManager, callback])
  return keyManager;
}

\