一个优雅的管理 keyupkeydown 键盘事件的 Hook,支持键盘组合键,定义键盘事件的 keykeyCode 别名输入 。

import { useEffect, useCallback } from 'react';

type KeyPredicate = (event: KeyboardEvent) => boolean;
type keyType = KeyboardEvent['keyCode'] | KeyboardEvent['key'];
type KeyFilter = keyType | Array<keyType> | ((event: KeyboardEvent) => boolean);
type EventHandler = (event: KeyboardEvent) => void;
type keyEvent = 'keydown' | 'keyup';

// 键盘事件 keyCode 别名
const aliasKeyCodeMap: any = {
  esc: 27,
  tab: 9,
  enter: 13,
  space: 32,
  up: 38,
  left: 37,
  right: 39,
  down: 40,
  delete: [8, 46],
};

// 键盘事件 key 别名
const aliasKeyMap: any = {
  esc: 'Escape',
  tab: 'Tab',
  enter: 'Enter',
  space: ' ',
  // IE11 uses key names without `Arrow` prefix for arrow keys.
  up: ['Up', 'ArrowUp'],
  left: ['Left', 'ArrowLeft'],
  right: ['Right', 'ArrowRight'],
  down: ['Down', 'ArrowDown'],
  delete: ['Backspace', 'Delete'],
};

// 修饰键
const modifierKey: any = {
  ctrl: (event: KeyboardEvent) => event.ctrlKey,
  shift: (event: KeyboardEvent) => event.shiftKey,
  alt: (event: KeyboardEvent) => event.altKey,
  meta: (event: KeyboardEvent) => event.metaKey,
};

// 返回空对象
const noop = () => {};

/**
 * 判断对象类型
 * @param [obj: any] 参数对象
 * @returns String
 */
function isType(obj: any) {
  return Object.prototype.toString
    .call(obj)
    .replace(/^\[object (.+)\]$/, '$1')
    .toLowerCase();
}

/**
 * 判断按键是否激活
 * @param [event: KeyboardEvent]键盘事件
 * @param [keyFilter: any] 当前键
 * @returns Boolean
 */
function genFilterKey(event: any, keyFilter: any) {
  const type = isType(keyFilter);
  // 数字类型直接匹配事件的 keyCode
  if (type === 'number') {
    return event.keyCode === keyFilter;
  }
  // 字符串依次判断是否有组合键
  const genArr = keyFilter.split('.');
  let genLen = 0;
  for (const key of genArr) {
    // 组合键
    const genModifier = modifierKey[key];
    // key 别名
    const aliasKey = aliasKeyMap[key];
    // keyCode 别名
    const aliasKeyCode = aliasKeyCodeMap[key];
    /**
     * 满足以上规则
     * 1. 自定义组合键别名
     * 2. 自定义 key 别名
     * 3. 自定义 keyCode 别名
     * 4. 匹配 key 或 keyCode
     */
    if (
      (genModifier && genModifier(event)) ||
      (aliasKey && isType(aliasKey) === 'array'
        ? aliasKey.includes(event.key)
        : aliasKey === event.key) ||
      (aliasKeyCode && isType(aliasKeyCode) === 'array'
        ? aliasKeyCode.includes(event.keyCode)
        : aliasKeyCode === event.keyCode) ||
      event.key.toUpperCase() === key.toUpperCase()
    ) {
      genLen++;
    }
  }
  return genLen === genArr.length;
}

/**
 * 键盘输入预处理方法
 * @param [keyFilter: any] 当前键
 * @returns () => Boolean
 */
function genKeyFormater(keyFilter: any): KeyPredicate {
  const type = isType(keyFilter);
  if (type === 'function') {
    return keyFilter;
  }
  if (type === 'string' || type === 'number') {
    return (event: KeyboardEvent) => genFilterKey(event, keyFilter);
  }
  if (type === 'array') {
    return (event: KeyboardEvent) => keyFilter.some((item: any) => genFilterKey(event, item));
  }
  return keyFilter ? () => true : () => false;
}

const defaultEvents: Array<keyEvent> = ['keydown'];

function useKeyPress(
  keyFilter: KeyFilter,
  eventHandler: EventHandler = noop,
  events: Array<keyEvent> = defaultEvents,
) {
  const callbackHandler: EventHandler = useCallback(
    (event: KeyboardEvent) => {
      const genGuard: KeyPredicate = genKeyFormater(keyFilter);
      if (genGuard(event)) {
        return eventHandler(event);
      }
    },
    [eventHandler],
  );

  useEffect(() => {
    for (const eventName of events) {
      window.addEventListener(eventName, callbackHandler);
    }
    return () => {
      for (const eventName of events) {
        window.removeEventListener(eventName, callbackHandler);
      }
    };
  }, [events, callbackHandler]);
}

export default useKeyPress;

reference

支持多种输入方式

useKeyPress(['tab', 'left', 'delete', '1',  65, 27, 'ctrl.alt.s'], (event) => {
    console.log(event)
});

useKeyPress('ctrl.alt.4', (event) => {
    console.log(event)
}, ['keyup']);

useKeyPress((event) => {
    // 预处理方法,可以在这里进行计算后返回 boolean
    if (event.type === 'keydown') {
          event.preventDefault()
    }
    return true
}, (event) => {
    console.log(event)
}, ['keydown', 'keyup']);