Next.js
React 키보드 단축키가 입력창에서 실행된 문제를 막은 짧은 튜토리얼
문서 전체에 등록한 단축키가 검색창의 글자 입력까지 가로채던 문제를 이벤트 대상 검사로 분리했습니다.
검색창에서 스페이스가 사라졌다
목록 화면에 /를 누르면 검색창에 포커스가 가고 Escape로 닫히는 단축키를 추가했다. 그런데 검색창에서 문장을 입력할 때 스페이스가 한 번씩 사라졌고, Escape를 누르면 모달이 닫혔다. window의 keydown 리스너가 모든 입력 이벤트를 보고 있었던 탓이다.
이벤트 대상에 예외를 둔다
단축키는 문서 전체에서 동작해야 하지만 input, textarea, select, 그리고 편집 가능한 요소에서는 입력의 의미를 우선해야 한다.
import { useEffect } from 'react'
export function useSearchShortcut(openSearch: () => void, closeSearch: () => void) {
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
const target = event.target as HTMLElement | null
const typing = target?.matches('input, textarea, select, [contenteditable="true"]')
if (typing && event.key !== 'Escape') return
if (event.key === '/') {
event.preventDefault()
openSearch()
}
if (event.key === 'Escape') closeSearch()
}
window.addEventListener('keydown', onKeyDown)
return () => window.removeEventListener('keydown', onKeyDown)
}, [openSearch, closeSearch])
}
처음에는 event.target instanceof HTMLInputElement만 검사했는데 textarea와 contenteditable 영역을 빠뜨렸다. 태그 이름을 나열하는 대신 matches 선택자로 관리하니 기준이 한곳에 모였다.
preventDefault를 아무 데서나 부르지 않는다
키 이벤트를 막는 코드가 앞에 있으면 입력창 안에서도 브라우저 기본 동작이 사라진다. 실제로 / 단축키가 아닐 때는 preventDefault를 호출하지 않도록 순서를 바꿨다. 또 함수가 렌더링마다 새로 만들어지는 컴포넌트에서는 의존성 배열을 비워 경고를 감추지 말고, 콜백의 안정성까지 확인해야 한다.
직접 확인한 테스트 목록
npm test -- --runInBand shortcut
수동 테스트는 일반 화면에서 /, 검색창에서 /, textarea에서 스페이스, contenteditable에서 글자 입력, 모달에서 Escape 순서로 고정했다. 이 정도면 단축키가 작동하는지보다 입력을 망가뜨리지 않는지를 먼저 볼 수 있다.
단축키는 기능 하나처럼 보여도 사용자의 입력 영역과 충돌한다. 대상 요소를 예외 처리하고 기본 동작을 필요한 순간에만 막는 것이 이 문제에서 정한 기준이다.