一个优雅的管理 keyup
和 keydown
键盘事件的 Hook,支持键盘组合键,定义键盘事件的 key
和 keyCode
别名输入 。
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']);