MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React 键盘事件处理与快捷键实现

2021-01-225.9k 阅读

React 键盘事件基础

在 React 应用程序中,处理键盘事件是一项常见的任务。键盘事件可以让用户通过键盘与应用进行交互,例如输入文本、触发操作等。React 提供了一套简单且一致的方式来处理这些事件。

常用键盘事件类型

  1. onKeyDown:当用户按下键盘上的某个键时触发。这个事件会在键按下的瞬间触发,并且在按住一个键时会持续触发。
  2. onKeyUp:当用户松开键盘上的某个键时触发。
  3. onKeyPress:当用户按下并释放一个可打印字符键(例如字母、数字、标点符号等)时触发。这个事件不会触发非打印字符键,如功能键(F1 - F12)、箭头键等。

事件对象

当这些键盘事件被触发时,React 会传递一个事件对象作为回调函数的参数。这个事件对象包含了许多有用的信息,例如按下的键的相关信息。

import React, { useState } from 'react';

const KeyboardEventExample = () => {
  const [keyInfo, setKeyInfo] = useState('');

  const handleKeyDown = (event) => {
    setKeyInfo(`Key ${event.key} was pressed.`);
  };

  return (
    <div>
      <input
        type="text"
        onKeyDown={handleKeyDown}
      />
      <p>{keyInfo}</p>
    </div>
  );
};

export default KeyboardEventExample;

在上述代码中,我们创建了一个简单的输入框,并为其添加了 onKeyDown 事件处理函数。当用户在输入框中按下任意键时,handleKeyDown 函数会被调用,它通过事件对象的 key 属性获取按下的键,并更新 keyInfo 状态,从而在页面上显示出按下的键的信息。

事件对象的属性

  1. key:返回按下或释放的键的字符表示。对于可打印字符,它通常是字符本身;对于非打印字符,如功能键,它会返回特定的字符串,如 'F1''ArrowUp' 等。
  2. code:返回按下或释放的键的物理键码。这个属性可以用来唯一标识一个物理按键,即使在不同的键盘布局下也能保持一致。例如,对于 A 键,无论键盘布局如何,code 可能是 'KeyA'
  3. charCode:仅在 onKeyPress 事件中有意义,返回按下的字符的 Unicode 代码点。在 onKeyDownonKeyUp 事件中,该属性始终为 0。
  4. which:在 onKeyDownonKeyUp 事件中,which 返回键的代码;在 onKeyPress 事件中,which 返回按下字符的 Unicode 代码点。在现代浏览器中,which 已被 keyCode(在 onKeyDownonKeyUp 中)和 charCode(在 onKeyPress 中)替代,但为了兼容性,有时仍会使用。
  5. shiftKeyctrlKeyaltKeymetaKey:这些布尔属性表示在触发事件时,对应的修饰键(Shift、Ctrl、Alt、Meta/Command)是否被按下。

处理特定键盘按键

在实际应用中,我们常常需要针对特定的键盘按键进行处理。比如,当用户按下回车键时提交表单,按下 Esc 键关闭弹窗等。

回车键处理

回车键在表单提交等场景中经常用到。假设我们有一个简单的登录表单,用户输入用户名和密码后,按下回车键可以提交表单。

import React, { useState } from 'react';

const LoginForm = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(`Username: ${username}, Password: ${password}`);
  };

  const handleKeyDown = (e) => {
    if (e.key === 'Enter') {
      handleSubmit(e);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
          onKeyDown={handleKeyDown}
        />
      </label>
      <br />
      <label>
        Password:
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          onKeyDown={handleKeyDown}
        />
      </label>
      <br />
      <button type="submit">Login</button>
    </form>
  );
};

export default LoginForm;

在上述代码中,我们为用户名和密码输入框都添加了 onKeyDown 事件处理函数 handleKeyDown。当用户按下回车键(e.key === 'Enter')时,会调用 handleSubmit 函数,该函数会阻止表单的默认提交行为,并在控制台打印出用户名和密码。

Esc 键处理

Esc 键通常用于关闭弹窗、取消操作等。假设我们有一个简单的弹窗组件,当用户按下 Esc 键时,弹窗会关闭。

import React, { useState } from 'react';

const Popup = () => {
  const [isOpen, setIsOpen] = useState(true);

  const handleKeyDown = (e) => {
    if (e.key === 'Escape') {
      setIsOpen(false);
    }
  };

  if (!isOpen) return null;

  return (
    <div
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100vw',
        height: '100vh',
        backgroundColor: 'rgba(0, 0, 0, 0.5)',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center'
      }}
      onKeyDown={handleKeyDown}
      tabIndex={-1}
    >
      <div
        style={{
          backgroundColor: 'white',
          padding: '20px',
          borderRadius: '5px'
        }}
      >
        <p>This is a popup. Press Esc to close.</p>
      </div>
    </div>
  );
};

export default Popup;

在这个弹窗组件中,我们在最外层的 div 上添加了 onKeyDown 事件处理函数 handleKeyDown。当用户按下 Esc 键(e.key === 'Escape')时,会调用 setIsOpen(false) 关闭弹窗。注意,我们还为最外层的 div 设置了 tabIndex={-1},这是为了确保该元素可以接收键盘焦点,从而能够捕获键盘事件。

功能键处理

功能键(F1 - F12)在不同的应用中有不同的用途。例如,F1 键常被用于打开帮助文档。

import React, { useState } from 'react';

const App = () => {
  const [helpVisible, setHelpVisible] = useState(false);

  const handleKeyDown = (e) => {
    if (e.key === 'F1') {
      setHelpVisible(!helpVisible);
    }
  };

  return (
    <div
      onKeyDown={handleKeyDown}
      tabIndex={-1}
    >
      {helpVisible && (
        <div
          style={{
            position: 'fixed',
            top: 0,
            left: 0,
            width: '100vw',
            height: '100vh',
            backgroundColor: 'rgba(0, 0, 0, 0.5)',
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center'
          }}
        >
          <div
            style={{
              backgroundColor: 'white',
              padding: '20px',
              borderRadius: '5px'
            }}
          >
            <p>This is the help documentation.</p>
          </div>
        </div>
      )}
      <p>Press F1 to show/hide help.</p>
    </div>
  );
};

export default App;

在上述代码中,当用户按下 F1 键时,会切换 helpVisible 的状态,从而显示或隐藏帮助文档。同样,为了捕获键盘事件,我们为最外层的 div 设置了 tabIndex={-1}

组合键与快捷键实现

快捷键通常是由一个或多个修饰键(如 Ctrl、Shift、Alt、Meta/Command)与其他键组合而成。在 React 中实现快捷键可以大大提高用户操作的效率。

简单组合键处理

例如,我们希望实现一个功能,当用户按下 Ctrl + S(在 Mac 上是 Command + S)时,保存当前页面的内容。

import React, { useState } from 'react';

const App = () => {
  const [content, setContent] = useState('');

  const handleKeyDown = (e) => {
    const isCtrlOrCmd = e.ctrlKey || e.metaKey;
    if (isCtrlOrCmd && e.key === 's') {
      console.log('Content saved:', content);
    }
  };

  return (
    <div
      onKeyDown={handleKeyDown}
      tabIndex={-1}
    >
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
      />
      <p>Press Ctrl + S (or Cmd + S on Mac) to save.</p>
    </div>
  );
};

export default App;

在上述代码中,我们在 handleKeyDown 函数中首先检查 e.ctrlKeye.metaKey 是否为 true,以确定用户是否按下了 Ctrl 键(在 Windows 和 Linux 上)或 Command 键(在 Mac 上)。然后,我们检查按下的键是否为 s。如果满足这两个条件,就模拟保存操作并在控制台打印出当前的内容。

复杂快捷键组合

有时候,我们可能需要处理更复杂的快捷键组合,例如 Ctrl + Shift + A

import React, { useState } from 'react';

const App = () => {
  const [message, setMessage] = useState('');

  const handleKeyDown = (e) => {
    const isCtrlOrCmd = e.ctrlKey || e.metaKey;
    if (isCtrlOrCmd && e.shiftKey && e.key === 'a') {
      setMessage('You pressed Ctrl + Shift + A');
    }
  };

  return (
    <div
      onKeyDown={handleKeyDown}
      tabIndex={-1}
    >
      <p>{message}</p>
      <p>Press Ctrl + Shift + A to see a message.</p>
    </div>
  );
};

export default App;

在这个例子中,handleKeyDown 函数检查 e.ctrlKeye.metaKeye.shiftKey 是否为 true,并且按下的键是否为 a。如果所有条件都满足,就更新 message 状态并在页面上显示相应的提示信息。

全局快捷键

在某些情况下,我们可能希望在整个应用程序中都能捕获快捷键,而不仅仅是在某个特定的组件上。要实现全局快捷键,我们可以在应用的根组件上添加键盘事件监听器。

import React, { useEffect } from 'react';

const App = () => {
  useEffect(() => {
    const handleKeyDown = (e) => {
      const isCtrlOrCmd = e.ctrlKey || e.metaKey;
      if (isCtrlOrCmd && e.key === 'f') {
        console.log('Global search triggered');
      }
    };

    document.addEventListener('keydown', handleKeyDown);

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, []);

  return (
    <div>
      <p>Press Ctrl + F (or Cmd + F on Mac) for global search.</p>
    </div>
  );
};

export default App;

在上述代码中,我们使用 useEffect 钩子在组件挂载时为 document 添加 keydown 事件监听器 handleKeyDown。在 handleKeyDown 函数中,我们检查是否按下了 Ctrl + F(或 Command + F 在 Mac 上)。当组件卸载时,通过返回的清理函数移除事件监听器,以避免内存泄漏。

键盘事件的冒泡与阻止

与其他 DOM 事件一样,键盘事件也遵循事件冒泡机制。这意味着当一个元素触发键盘事件时,该事件会向上冒泡到其父元素,直到到达文档根节点。

事件冒泡示例

import React from'react';

const OuterComponent = () => {
  const handleKeyDownOuter = (e) => {
    console.log('Outer component key down:', e.key);
  };

  return (
    <div
      onKeyDown={handleKeyDownOuter}
      style={{ border: '1px solid red', padding: '10px' }}
    >
      <InnerComponent />
    </div>
  );
};

const InnerComponent = () => {
  const handleKeyDownInner = (e) => {
    console.log('Inner component key down:', e.key);
  };

  return (
    <div
      onKeyDown={handleKeyDownInner}
      style={{ border: '1px solid blue', padding: '10px' }}
    >
      <input type="text" />
    </div>
  );
};

export default OuterComponent;

在上述代码中,当用户在输入框中按下一个键时,首先会触发 InnerComponentonKeyDown 事件处理函数,然后事件会冒泡到 OuterComponent,触发其 onKeyDown 事件处理函数。因此,在控制台中会依次打印出 “Inner component key down: [key]” 和 “Outer component key down: [key]”。

阻止事件冒泡

有时候,我们可能不希望事件冒泡到父元素。例如,在一个模态框中,我们可能希望键盘事件仅在模态框内部处理,而不影响到模态框之外的部分。

import React from'react';

const Modal = () => {
  const handleKeyDownModal = (e) => {
    console.log('Modal key down:', e.key);
    e.stopPropagation();
  };

  return (
    <div
      onKeyDown={handleKeyDownModal}
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100vw',
        height: '100vh',
        backgroundColor: 'rgba(0, 0, 0, 0.5)',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center'
      }}
    >
      <div
        style={{
          backgroundColor: 'white',
          padding: '20px',
          borderRadius: '5px'
        }}
      >
        <input type="text" />
      </div>
    </div>
  );
};

export default Modal;

在上述代码中,当用户在模态框内的输入框中按下一个键时,handleKeyDownModal 函数会被调用。通过调用 e.stopPropagation(),我们阻止了事件冒泡到父元素,因此不会触发父元素(如果有)的键盘事件处理函数。

键盘事件与无障碍访问

在前端开发中,考虑无障碍访问是非常重要的。键盘事件处理在实现无障碍访问方面起着关键作用,因为许多残障用户依赖键盘来浏览和操作网页。

确保可聚焦元素

为了让键盘能够与页面元素交互,元素必须是可聚焦的。通常,像链接(<a>)、按钮(<button>)、输入框(<input>)等元素默认是可聚焦的。但对于自定义组件,如果需要通过键盘交互,就需要确保它们是可聚焦的。

import React from'react';

const CustomComponent = () => {
  return (
    <div
      tabIndex={0}
      style={{ border: '1px solid green', padding: '10px' }}
    >
      <p>This is a custom component. It can be focused with the keyboard.</p>
    </div>
  );
};

export default CustomComponent;

在上述代码中,我们为自定义的 div 组件添加了 tabIndex={0},这使得该组件可以通过 Tab 键聚焦,从而能够接收键盘事件。

键盘导航与焦点管理

良好的键盘导航体验对于无障碍访问至关重要。我们需要确保用户可以通过键盘(如 Tab 键、Shift + Tab 键等)在可聚焦元素之间进行导航,并且焦点的移动逻辑是合理的。

import React, { useState } from'react';

const NavigationExample = () => {
  const [focusIndex, setFocusIndex] = useState(0);
  const items = ['Item 1', 'Item 2', 'Item 3'];

  const handleKeyDown = (e) => {
    if (e.key === 'ArrowUp') {
      setFocusIndex((prevIndex) => (prevIndex === 0? items.length - 1 : prevIndex - 1));
    } else if (e.key === 'ArrowDown') {
      setFocusIndex((prevIndex) => (prevIndex === items.length - 1? 0 : prevIndex + 1));
    }
  };

  return (
    <div
      onKeyDown={handleKeyDown}
      tabIndex={0}
    >
      {items.map((item, index) => (
        <div
          key={index}
          style={{
            border: '1px solid gray',
            padding: '10px',
            backgroundColor: focusIndex === index? 'lightblue' : 'white'
          }}
        >
          {item}
        </div>
      ))}
    </div>
  );
};

export default NavigationExample;

在这个例子中,我们创建了一个简单的导航组件。用户可以通过上下箭头键在不同的项目之间移动焦点,并且当前焦点所在的项目会有不同的背景颜色,以提供视觉反馈。通过合理的键盘事件处理和焦点管理,我们为用户提供了良好的键盘导航体验。

提供键盘操作提示

为了帮助用户了解如何通过键盘操作应用程序,我们可以在页面上提供相应的提示信息。

import React from'react';

const KeyboardHintExample = () => {
  return (
    <div>
      <p>Press Tab to move between fields, Enter to submit.</p>
      <input type="text" />
      <input type="password" />
      <button type="submit">Submit</button>
    </div>
  );
};

export default KeyboardHintExample;

在上述代码中,我们在页面上显示了一条提示信息,告知用户可以使用 Tab 键在输入框和按钮之间移动焦点,使用 Enter 键提交表单。这样可以帮助用户更好地使用键盘与应用进行交互。

通过合理地处理键盘事件、管理焦点以及提供键盘操作提示,我们可以大大提高应用程序的无障碍访问性,使得更多用户能够方便地使用我们的应用。