{"action":"created","issue":{"url":"https://api.github.com/repos/umijs/hooks/issues/163","repository_url":"https://api.github.com/repos/umijs/hooks","labels_url":"https://api.github.com/repos/umijs/hooks/issues/163/labels{/name}","comments_url":"https://api.github.com/repos/umijs/hooks/issues/163/comments","events_url":"https://api.github.com/repos/umijs/hooks/issues/163/events","html_url":"https://github.com/umijs/hooks/issues/163","id":529258616,"node_id":"MDU6SXNzdWU1MjkyNTg2MTY=","number":163,"title":"useKeyPress","user":{"login":"luchx","id":17548394,"node_id":"MDQ6VXNlcjE3NTQ4Mzk0","avatar_url":"https://avatars0.githubusercontent.com/u/17548394?v=4","gravatar_id":"","url":"https://api.github.com/users/luchx","html_url":"https://github.com/luchx","followers_url":"https://api.github.com/users/luchx/followers","following_url":"https://api.github.com/users/luchx/following{/other_user}","gists_url":"https://api.github.com/users/luchx/gists{/gist_id}","starred_url":"https://api.github.com/users/luchx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/luchx/subscriptions","organizations_url":"https://api.github.com/users/luchx/orgs","repos_url":"https://api.github.com/users/luchx/repos","events_url":"https://api.github.com/users/luchx/events{/privacy}","received_events_url":"https://api.github.com/users/luchx/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2019-11-27T10:34:31Z","updated_at":"2019-11-27T12:40:07Z","closed_at":null,"author_association":"CONTRIBUTOR","body":"一个优雅的管理 `keyup` 和 `keydown` 键盘事件的 Hook，支持键盘组合键，定义键盘事件的 `key`  和 ` keyCode` 别名输入 。\r\n\r\n```ts\r\nimport { useEffect } from 'react';\r\n\r\ntype KeyPredicate = (event: KeyboardEvent) => boolean;\r\ntype keyType = KeyboardEvent[\"keyCode\"] | KeyboardEvent[\"key\"];\r\ntype KeyMap = keyType | Array<keyType> | ((event: KeyboardEvent) => boolean);\r\ntype EventHandler = (event: KeyboardEvent) => void;\r\ntype keyEnum = 'keydown' | 'keyup';\r\n\r\n// 键盘事件 keyCode 别名\r\nconst aliasKeyCodeMap: any = {\r\n  esc: 27,\r\n  tab: 9,\r\n  enter: 13,\r\n  space: 32,\r\n  up: 38,\r\n  left: 37,\r\n  right: 39,\r\n  down: 40,\r\n  'delete': [8, 46]\r\n};\r\n\r\n// 键盘事件 key 别名\r\nconst aliasKeyMap: any = {\r\n  esc: 'Escape',\r\n  tab: 'Tab',\r\n  enter: 'Enter',\r\n  space: ' ',\r\n  // IE11 uses key names without `Arrow` prefix for arrow keys.\r\n  up: ['Up', 'ArrowUp'],\r\n  left: ['Left', 'ArrowLeft'],\r\n  right: ['Right', 'ArrowRight'],\r\n  down: ['Down', 'ArrowDown'],\r\n  'delete': ['Backspace', 'Delete']\r\n};\r\n\r\n// 键盘事件 组合键别名\r\nconst combineKey: any = {\r\n  ctrl: (event: KeyboardEvent) => event.ctrlKey,\r\n  shift: (event: KeyboardEvent) => event.shiftKey,\r\n  alt: (event: KeyboardEvent) => event.altKey,\r\n  meta: (event: KeyboardEvent) => event.metaKey,\r\n};\r\n\r\n// 返回空对象\r\nconst noop = () => { };\r\n\r\n/**\r\n * 判断对象类型\r\n * @param [obj: any] 参数对象\r\n * @returns String\r\n */\r\nfunction isType(obj: any) {\r\n  return Object.prototype.toString.call(obj).replace(/^\\[object (.+)\\]$/, '$1').toLowerCase();\r\n}\r\n\r\n/**\r\n * 字符串转数字，如果失败则返回字符串\r\n * @param [val: string] 字符串参数\r\n * @returns Boolean\r\n */\r\nfunction toNumber(val: string) {\r\n  var n = parseFloat(val);\r\n  return isNaN(n) ? val : n\r\n}\r\n\r\n/**\r\n * 判断按键是否激活\r\n * @param [event: KeyboardEvent]键盘事件\r\n * @param [keyMap: any] 当前键\r\n * @returns Boolean\r\n */\r\nfunction genFilterKey(event: any, keyMap: any) {\r\n  const type = isType(keyMap);\r\n  // 数字类型直接匹配事件的 keyCode\r\n  if (type === 'number') {\r\n    return event.keyCode === keyMap\r\n  }\r\n  // 字符串依次判断是否有组合键\r\n  const genArr = keyMap.split('.');\r\n  let genLen = 0;\r\n  for (const key of genArr) {\r\n    // 组合键\r\n    const genModified = combineKey[key];\r\n    // key 别名\r\n    const aliasKey = aliasKeyMap[key];\r\n    // keyCode 别名\r\n    const aliasKeyCode = aliasKeyCodeMap[key];\r\n    /**\r\n     * 满足以上规则\r\n     * 1. 自定义组合键别名\r\n     * 2. 自定义 key 别名\r\n     * 3. 自定义 keyCode 别名\r\n     * 4. 匹配 key 或 keyCode\r\n     */\r\n    if (\r\n      (genModified && genModified(event))\r\n      || (aliasKey\r\n        && isType(aliasKey) === 'array'\r\n        ? aliasKey.includes(event.key)\r\n        : aliasKey === event.key)\r\n      || (aliasKeyCode\r\n        && isType(aliasKeyCode) === 'array'\r\n        ? aliasKeyCode.includes(event.keyCode)\r\n        : aliasKeyCode === event.keyCode)\r\n      || event.key === key\r\n      || event.keyCode === toNumber(key)\r\n    ) {\r\n      genLen++\r\n    }\r\n  }\r\n\r\n  return genLen === genArr.length;\r\n}\r\n\r\n/**\r\n * 键盘输入预处理方法\r\n * @param [keyMap: any] 当前键\r\n * @returns () => Boolean\r\n */\r\nfunction genKeyFormater(keyMap: any): KeyPredicate {\r\n  const type = isType(keyMap);\r\n  if (type === 'function') {\r\n    return keyMap\r\n  }\r\n  if (type === 'string' || type === 'number') {\r\n    return (event: KeyboardEvent) => genFilterKey(event, keyMap);\r\n  }\r\n  if (type === 'array') {\r\n    return (event: KeyboardEvent) => keyMap.some((item: any) => genFilterKey(event, item))\r\n  }\r\n  return keyMap ? () => true : () => false\r\n};\r\n\r\nconst defaultEvents: Array<keyEnum> = ['keydown'];\r\n\r\nfunction useKeyPress(keyMap: KeyMap, eventHandler: EventHandler = noop, events = defaultEvents) {\r\n  useEffect(() => {\r\n    const callback: EventHandler = handlerEvent => {\r\n      const genGuard: KeyPredicate = genKeyFormater(keyMap);\r\n      if (genGuard(handlerEvent)) {\r\n        return eventHandler(handlerEvent);\r\n      }\r\n    };\r\n\r\n    for (const eventName of events) {\r\n      window.addEventListener(eventName, callback);\r\n    }\r\n    return () => {\r\n      for (const eventName of events) {\r\n        window.removeEventListener(eventName, callback);\r\n      }\r\n    };\r\n  }, [events, eventHandler]);\r\n};\r\n\r\nexport default useKeyPress;\r\n\r\n```\r\n\r\n## reference\r\n\r\n> 支持多种输入方式\r\n\r\n```\r\nuseKeyPress(['tab', 'left', 'delete', '1',  65, 27, 'ctrl.alt.s'], (event) => {\r\n    console.log(event)\r\n});\r\n\r\nuseKeyPress('ctrl.alt.4', (event) => {\r\n    console.log(event)\r\n}, ['keyup']);\r\n\r\nuseKeyPress((event) => {\r\n    // 预处理方法，可以在这里进行计算后返回 boolean\r\n    return true\r\n}, (event) => {\r\n    console.log(event)\r\n}, ['keydown', 'keyup']);\r\n```"},"comment":{"url":"https://api.github.com/repos/umijs/hooks/issues/comments/559071181","html_url":"https://github.com/umijs/hooks/issues/163#issuecomment-559071181","issue_url":"https://api.github.com/repos/umijs/hooks/issues/163","id":559071181,"node_id":"MDEyOklzc3VlQ29tbWVudDU1OTA3MTE4MQ==","user":{"login":"brickspert","id":12526493,"node_id":"MDQ6VXNlcjEyNTI2NDkz","avatar_url":"https://avatars0.githubusercontent.com/u/12526493?v=4","gravatar_id":"","url":"https://api.github.com/users/brickspert","html_url":"https://github.com/brickspert","followers_url":"https://api.github.com/users/brickspert/followers","following_url":"https://api.github.com/users/brickspert/following{/other_user}","gists_url":"https://api.github.com/users/brickspert/gists{/gist_id}","starred_url":"https://api.github.com/users/brickspert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brickspert/subscriptions","organizations_url":"https://api.github.com/users/brickspert/orgs","repos_url":"https://api.github.com/users/brickspert/repos","events_url":"https://api.github.com/users/brickspert/events{/privacy}","received_events_url":"https://api.github.com/users/brickspert/received_events","type":"User","site_admin":false},"created_at":"2019-11-27T12:40:07Z","updated_at":"2019-11-27T12:40:07Z","author_association":"COLLABORATOR","body":"看 API 超级棒。 可以直接贡献了~"}}
{"actor":{"display_login":"brickspert"}}