如何避免使用超过所需键的快捷方式?

How can I avoid shortcut working whit more than required keys?

我正在使用以下挂钩在我的应用程序中使用快捷方式:
(我从这个 repo 改编而来:https://github.com/arthurtyukayev/use-keyboard-shortcut

 import { useEffect, useCallback, useReducer } from 'react';

const disabledEventPropagation = e => {
  if (e)
    if (e.stopPropagation) {
      e.stopPropagation();
    } else if (window.event) {
      window.event.cancelBubble = true;
    }
};

const blacklistedTargets = ['INPUT', 'TEXTAREA'];

const keysReducer = (state, action) => {
  switch (action.type) {
    case 'set-key-down':
      const keydownState = { ...state, [action.key]: true };
      return keydownState;
    case 'set-key-up':
      const keyUpState = { ...state, [action.key]: false };
      return keyUpState;
    case 'reset-keys':
      const resetState = { ...action.data };
      return resetState;
    default:
      return state;
  }
};

const useKeyboardShortcut = (shortcutKeys, callback, options) => {
  if (!Array.isArray(shortcutKeys))
    throw new Error('The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings.');

  if (!shortcutKeys.length)
    throw new Error('The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string.');

  if (!callback || typeof callback !== 'function')
    throw new Error('The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed.');

  const { overrideSystem } = options || {};
  const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
    currentKeys[key.toLowerCase()] = false;
    return currentKeys;
  }, {});

  const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);

  const keydownListener = useCallback(
    assignedKey => keydownEvent => {
      const loweredKey = assignedKey.toLowerCase();

      if (keydownEvent.repeat) return;
      if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
      if (loweredKey !== keydownEvent.key.toLowerCase()) return;
      if (keys[loweredKey] === undefined) return;

      if (overrideSystem) {
        keydownEvent.preventDefault();
        disabledEventPropagation(keydownEvent);
      }

      setKeys({ type: 'set-key-down', key: loweredKey });
      return false;
    },
    [keys, overrideSystem],
  );

  const keyupListener = useCallback(
    assignedKey => keyupEvent => {
      const raisedKey = assignedKey.toLowerCase();

      if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
      if (keyupEvent.key.toLowerCase() !== raisedKey) return;
      if (keys[raisedKey] === undefined) return;

      if (overrideSystem) {
        keyupEvent.preventDefault();
        disabledEventPropagation(keyupEvent);
      }

      setKeys({ type: 'set-key-up', key: raisedKey });
      return false;
    },
    [keys, overrideSystem],
  );

  useEffect(() => {
    if (!Object.values(keys).filter(value => !value).length) {
      callback(keys);
      const newKeyMapping = initalKeyMapping;
      newKeyMapping.control = true;
      setKeys({ type: 'reset-keys', data: initalKeyMapping });
    } else setKeys({ type: null });
  }, [callback, keys]);

  useEffect(() => {
    shortcutKeys.forEach(k => window.addEventListener('keydown', keydownListener(k)));
    return () => shortcutKeys.forEach(k => window.removeEventListener('keydown', keydownListener(k)));
  }, []);

  useEffect(() => {
    shortcutKeys.forEach(k => window.addEventListener('keyup', keyupListener(k)));
    return () => shortcutKeys.forEach(k => window.removeEventListener('keyup', keyupListener(k)));
  }, []);
};

export default useKeyboardShortcut;

因此,我使用以下行来创建和处理快捷方式:

const shortcutUndo = ['Control', 'Z'];
useKeyboardShortcut(shortcutUndo, handleUndoShortcut);

问题是我每次按 Ctrl+Z+[任何其他键] 时,ctr+z 快捷键仍然是 运行,但它不应该是 运行ning。我想不出任何办法来防止这种情况,有人知道吗?

您可以通过重新考虑快捷方式的触发方式来解决此问题。更准确地说,您的钩子可以执行以下操作:

let sequence: string[] = [];

function onKeyDown(key: string) {
    // Push key to sequence
    sequence.push(key);
}

function onKeyUp(key: string) {
    // Store the sequence before we pop from it
    const seq = [...sequence];
    const popped = sequence.pop(); // last pressed-down key

    if (popped === undefined) return; // Sequence was already empty (dropped)
    if (popped !== key) {
        // User didn't release the last key, drop the whole sequence
        sequence = [];
    }

    // Check whether the sequence matches
    if (arrayEquals(seq, shortcutKeys)) {
        // Shortcut got triggered, do whatever
    }
}
}

这只是按键逻辑,我省略了所有 React 和 hook 相关的东西

这个想法是,当用户不断按下按键时,您会存储按下的按键序列。然后当用户释放最后按下的键时,我们检查是否按下了正确的序列。如果用户释放了一个完全不同的键,我们将重置整个序列以确保安全 (尽管您可能只想删除未按下的键).

这会导致(取消)按下以下键时发生以下情况:

let sequence = [];

const shortcutKeys = ['Control', 'Z'];

function onKeyDown(key) {
    // Push key to sequence
    sequence.push(key);
    console.log('onKeyDown', key, '-> [' + sequence.join('+') + ']');
}

function onKeyUp(key) {
    console.log('onKeyUp', key);
    // Store the sequence before we pop from it
    const seq = [...sequence];
    const popped = sequence.pop(); // last pressed-down key

    if (popped === undefined) return; // Sequence was already empty (dropped)
    if (popped !== key) {
        // User didn't release the last key, drop the whole sequence
        sequence = [];
        console.log('\tSequence dropped');
        return;
    }

    console.log('\t->', '[' + sequence.join('+') + ']');

    // Check whether the sequence matches
    if (seq.join('+') === shortcutKeys.join('+')) {
        // Shortcut got triggered, do whatever
        console.log('Shortcut triggered!');
    }
}

console.log('== Example 1 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');
// Shortcut triggered once

console.log('== Example 2 ==');
onKeyDown('Control');
onKeyDown('Shift');
onKeyUp('Shift');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');

console.log('== Example 3 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');

console.log('== Example 4 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyDown('S');
onKeyDown('S');
onKeyUp('Z');
onKeyUp('Control');
// Shortcut never triggered

console.log('== Example 5 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Control');
onKeyUp('Z');
// Shortcut never triggered

这解决了您的问题,同时还允许用户“发送垃圾邮件”快捷方式,而无需重复整个序列。

您可以做的一个小改动是将 arrayEquals 替换为仅检查组合是否存在但不关心顺序的内容。例如。这将使 Ctrl+Shift+ZShift+Ctrl+Z(还有 Ctrl+Z+Shift)都相等。