- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
useMagicKeys是 VueUse(位于 packages/core/useMagicKeys)中一个用于监听键盘状态的 Sensor 类工具函数,它提供响应式的按键按下状态,并支持通过+或_连接多个键名来魔法式地声明组合键(快捷键/热键)。本文以官方文档 useMagicKeys.md 为主体,结合仓库源码与测试用例,系统讲解其用法、配置项与底层实现原理,帮助读者在 Vue 3 项目中轻松实现快捷键监听、组合键判定、条件触发与输入框聚焦场景下的按键拦截。
基本用法:响应式追踪单个按键
useMagicKeys从@vueuse/core导出,返回一个对象,其属性对应你关心的按键名,每个属性都是ComputedRef<boolean>(默认模式):
import { useMagicKeys } from '@vueuse/core' const { shift, space, a /* keys you want to monitor */ } = useMagicKeys() watch(space, (v) => { if (v) console.log('space has been pressed') }) watchEffect(() => { if (shift.value && a.value) console.log('Shift + A have been pressed') })按键名统一使用小写(源码中会执行e.key?.toLowerCase()与e.code?.toLowerCase()),例如空格键space、字母a、修饰键shift。值得注意的是,useMagicKeys的属性访问是通过索引签名动态支持的,你可以直接解构任意你想监控的键,而不需要预先声明,这是其"magic"体验的来源(详见下文源码解析中的Proxy机制)。
TypeScript 提示:noUncheckedIndexedAccess
如果你在tsconfig.json中开启了noUncheckedIndexedAccess(Nuxt 默认开启),由于useMagicKeys()通过索引签名允许动态访问任意键,TypeScript 会把解构出来的属性类型推断为ComputedRef<boolean> | undefined。
此时需要使用可选链或包裹 getter 函数来访问:
const { shift, space, a } = useMagicKeys() watch( () => space?.value, (v) => { if (v) console.log('space has been pressed') }, ) watchEffect(() => { if (shift?.value && a?.value) console.log('Shift + A have been pressed') })组合键:用+或_连接键名
组合键(快捷键/热键)是useMagicKeys最具特色的能力:用+或_将多个键名连接起来,即可得到一个新的响应式布尔值,表示这些键是否同时被按下。
import { useMagicKeys } from '@vueuse/core' const keys = useMagicKeys() const shiftCtrlA = keys['Shift+Ctrl+A'] watch(shiftCtrlA, (v) => { if (v) console.log('Shift + Ctrl + A have been pressed') })也支持直接解构下划线连接的组合键:
import { useMagicKeys } from '@vueuse/core' const { Ctrl_A_B, space, alt_s /* ... */ } = useMagicKeys() watch(Ctrl_A_B, (v) => { if (v) console.log('Control+A+B have been pressed') })组合键的判定顺序不敏感:源码中组合键会拆分成键名数组,并计算每个键对应 ref 值的every(Boolean)(见 index.ts),因此无论是先按 Shift 还是先按 Ctrl,只要最终状态同时成立即为true——这一点也被测试用例multiple keys(in a different order)所验证(index.browser.test.ts)。
配合 whenever 简化监听
VueUse 提供了whenever工具函数,专门用于"当某个值为真时执行回调",配合组合键可以大幅简化代码:
import { useMagicKeys, whenever } from '@vueuse/core' const keys = useMagicKeys() whenever(keys.shift_space, () => { console.log('Shift+Space have been pressed') })whenever底层封装了watch,仅在条件为真时触发回调(whenever/index.ts)。
current:当前所有被按下的键
useMagicKeys()返回对象中包含一个特殊的current属性,它是一个Set<string>(存储原始按键名),表示当前所有被按下的键:
import { useMagicKeys, whenever } from '@vueuse/core' const { current } = useMagicKeys() console.log(current) // Set { 'control', 'a' } whenever( () => current.has('a') && !current.has('b'), () => console.log('A is pressed but not B'), )由于current是响应式Set(源码中为reactive(new Set<string>()),见 index.ts),对其调用.has()、.size等操作都能触发响应式依赖收集,可以直接放入whenever/watchEffect的 getter 中组合任意按键状态逻辑。测试用例current return value也验证了current.has('v')与v.value同步更新(index.browser.test.ts)。
键别名:aliasMap
有些键名过长或不符合直觉,可以通过aliasMap配置自定义别名。注意别名键必须使用小写,映射格式为{ 别名: 键名 }:
import { useMagicKeys, whenever } from '@vueuse/core' const { shift_cool } = useMagicKeys({ aliasMap: { cool: 'space', }, }) whenever(shift_cool, () => console.log('Shift + Space have been pressed'))VueUse 内置了预配置的常用别名映射(aliasMap.ts),例如ctrl对应control、cmd/command对应meta、option对应alt、方向键的up/down/left/right对应arrowup/arrowdown/arrowleft/arrowright。完整默认别名如下:
| 别名 | 实际键名 | 说明 |
|---|---|---|
ctrl | control | Ctrl 键 |
command/cmd | meta | macOS Command 键 |
option | alt | macOS Option 键 |
up | arrowup | 上方向键 |
down | arrowdown | 下方向键 |
left | arrowleft | 左方向键 |
right | arrowright | 右方向键 |
由于内置别名已覆盖ctrl、cmd等常见写法,你在组合键中直接写Ctrl_A、shift_cmd_x等都是有效的。默认别名可通过DefaultMagicKeysAliasMap导出获取(见 index.ts)。
条件禁用:聚焦输入框时不触发快捷键
实际应用中,当用户正在<input />、<textarea />等元素中输入时,通常不希望快捷键响应(例如按 Tab 切换焦点)。官方文档给出了一种优雅的组合方案:用useActiveElement追踪当前激活元素,再用@vueuse/math的logicAnd对多个条件做逻辑与:
import { useActiveElement, useMagicKeys, whenever } from '@vueuse/core' import { logicAnd } from '@vueuse/math' const activeElement = useActiveElement() const notUsingInput = computed(() => activeElement.value?.tagName !== 'INPUT' && activeElement.value?.tagName !== 'TEXTAREA',) const { tab } = useMagicKeys() whenever(logicAnd(tab, notUsingInput), () => { console.log('Tab has been pressed outside of inputs!') })logicAnd的源码非常简洁:computed(() => args.every(i => toValue(i))),即所有参数(ref/getter)都为真时才为真(logicAnd/index.ts)。useActiveElement则响应式地返回document.activeElement(useActiveElement/index.ts)。两者组合即可实现"仅在非输入框聚焦时响应 Tab 键"的精准控制。
自定义事件处理:onEventFired
useMagicKeys支持传入onEventFired回调,在每次 keydown/keyup 事件触发时执行自定义逻辑,例如拦截浏览器的默认行为(如阻止 Ctrl+S 保存页面):
import { useMagicKeys, whenever } from '@vueuse/core' const { ctrl_s } = useMagicKeys({ passive: false, onEventFired(e) { if (e.ctrlKey && e.key === 's' && e.type === 'keydown') e.preventDefault() }, }) whenever(ctrl_s, () => console.log('Ctrl+S have been pressed'))⚠️ 官方文档明确警告:此用法不推荐,请谨慎使用。因为监听器默认是
passive: true(不拦截默认行为),要调用e.preventDefault()必须显式传入passive: false;而且全局拦截按键默认行为会影响整个页面,务必确认业务确实需要。源码中onEventFired的返回值会直接作为事件监听器回调的返回(index.ts)。
响应式模式:reactive: true
默认模式下返回值是"一坨 refs",即每个属性都是ComputedRef<boolean>,需要在模板中写keys.shift(会自动解包)或脚本中keys.shift.value。如果希望返回的是一个真正的响应式对象(属性直接是boolean),可以开启reactive: true:
import { useMagicKeys } from '@vueuse/core' const keys = useMagicKeys({ reactive: true })<template> <div v-if="keys.shift"> You are holding the Shift key! </div> </template>源码中对两种模式的处理清晰可见:const refs = useReactive ? reactive(obj) : obj,且组合键 ref 在非响应式模式下是ComputedRef、单键是shallowRef(index.ts)。开启响应式模式后,模板中的v-if="keys.shift"和keys.current.has('a')都能直接工作。
完整配置项速查
根据类型声明(index.ts),UseMagicKeysOptions全部配置项如下:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
reactive | boolean | false | 是否返回响应式对象而非 refs 对象 |
target | MaybeRefOrGetter<EventTarget> | window | 监听事件的目标元素,可传入 ref 或 getter |
aliasMap | Record<string, string> | 内置默认别名表 | 键别名映射,所有键名必须小写,如{ ctrl: "control" } |
passive | boolean | true | 是否注册 passive 监听器;需要preventDefault()时须设为false |
onEventFired | (e: KeyboardEvent) => void \| boolean | noop | keydown/keyup 的自定义事件处理回调 |
其中target支持MaybeRefOrGetter,意味着你可以把监听目标绑定到特定元素(如某个输入框),这在将快捷键限定在局部区域时很有用——测试用例中正是通过{ target }把监听挂到HTMLInputElement上完成单元测试(index.browser.test.ts)。
源码级原理剖析
useMagicKeys的实现位于 packages/core/useMagicKeys/index.ts,核心机制可以归纳为三点:
1. Proxy 按需懒创建 ref
返回对象是一个Proxy包装的refs容器。当你访问任意属性名(如keys['Shift+Ctrl+A']或解构Ctrl_A_B)时:
- 属性名先转为小写,若命中
aliasMap则替换为真实键名; - 若属性名包含
+/-/_,则按这些分隔符拆分成键名数组,动态创建一个computed(() => keys.map(key => toValue(proxy[key])).every(Boolean)),即组合键判定; - 否则创建一个
shallowRef(false)作为单键状态。
也就是说,你访问什么键,它才创建对应 ref,没有访问的键不会产生任何计算开销(index.ts)。
2. 按键状态的维护与修饰键依赖追踪
updateRefs在每次 keydown/keyup 时执行:同时把e.key和e.code的小写形式都写入对应 ref(所以Control与control都能命中),并维护currentSet。实现中有一个depsMap(Meta、Shift、Alt三个修饰键各对应一个依赖键集合),通过e.getModifierState()记录"在修饰键按下期间"被按下的键,从而在释放 Shift/Alt 时只清理之后才按下的键、避免误清其他按键——测试用例prevent incorrect clearing of other keys after releasing shift正是验证了这个行为(index.browser.test.ts)。
3. 针对 macOS Meta 键的兼容处理
由于 macOS 上释放 Meta(Command)键时浏览器可能不触发 keyup 事件,源码特别处理:当监测到 Meta 释放(或 window blur/focus)时,会手动清空metaDeps中记录的组合键状态(index.ts)。同时,在窗口失去/获得焦点(blur/focus)时执行reset()清空所有按键状态,避免用户切换窗口后状态残留(index.ts)。这两处分别在测试multiple keys(for MacOS meta won't trigger keyup)与target blur/target focus中有对应覆盖。
4. 健壮性处理
测试用例还覆盖了空key、空字符串 key 以及key为undefined等异常情况,源码在updateRefs中通过if (!key) return提前返回,确保这些边缘事件不会抛错(index.ts)。
最佳实践小结
- 组合键优先用
whenever+ 下划线命名,如keys.shift_space,可读性好且代码最短; - 输入场景务必做条件禁用:结合
useActiveElement与logicAnd判断焦点元素,避免快捷键干扰表单输入; - 需要
preventDefault时:必须显式设置passive: false,并尽量缩小onEventFired的拦截范围,官方不推荐全局拦截; - 模板渲染:需要直接在模板中读取按键状态时,开启
reactive: true更顺手;脚本逻辑中使用默认的 refs 模式即可,还能利用.value语义清晰的响应式链; - 开启
noUncheckedIndexedAccess的项目(含 Nuxt):访问解构出的按键 ref 时用可选链space?.value,保持类型安全。
- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
相关推荐
VueUse useMagicKeys 实战指南:基于组合键的响应式键盘状态监听
VueUse useMagicKeys 实战指南:基于组合键的响应式键盘状态监听 useMagicKeys 是 VueUse 核心包中用于监听键盘状态的组合式函
前端AIRI 实战指南:用 VueUse useMagicKeys 构建响应式键盘快捷键系统
AIRI 实战指南:用 VueUse useMagicKeys 构建响应式键盘快捷键系统 useMagicKeys 是 VueUse 中"响应式按键状态"组合式
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染VueUse useKeyModifier 完全指南:响应式监听 CapsLock 与修饰键状态
VueUse useKeyModifier 完全指南:响应式监听 CapsLock 与修饰键状态 useKeyModifier 是 VueUse(Vue 3 C
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考