안녕하세요 메이토입니다 ..!
때는 저번주 금요일,, 평소와 같이 '영화 리뷰' 라는 레벨 1 마지막 미션 피드백을 반영하던 중 사건이 발생합니다

리뷰어가 남긴 리뷰를 보니까,, API 호출이 두 번 된다는건데 내가 놓친 부분이 있구나 싶어 테스트 해보니,,


저는 계속 hello로 테스트를 해봤는데, 같은 현상이 전혀 재현이 안되더라구요.? 그래서 리뷰어에게 여쭤본 후 '아이언맨' 이라는 검색어로
검색을 했더니 ..


드디어 재현이 되었어요 !!
이제 원인을 알아야 해결을 하겠죠?! 지나가던 (퇴근하지 않으신) 코치를 붙잡고 ,, 이 황당한 사태에 대해 여쭤보니,,
한글이었기 때문에 발생한 문제다.!
라는 결론이 나오게 됩니다.
(참고) 저는 enter 키를 누르면 검색되도록 하기 위하여 keydown으로 설정을 했고, keyboardEvent가 Enter 일 경우 getSearchResult 라는 api 요청을 실행하는 로직이었습니다.
$input?.addEventListener("keydown", async (event) => {
const keyboardEvent = event as KeyboardEvent;
if (keyboardEvent.key === "Enter") {
const inputValue = (event.target as HTMLInputElement).value;
showSkeleton(20, "section");
if (inputValue === "") {
alert("검색어를 입력해주세요.");
} else {
const searchResult = await movieService.getSearchResult(inputValue);
/// 중략
}
}
});
근데 아마 저와 유사한 방식으로 코드를 짜신 분들이 있지 않을까 해서 이 기회에 이런 일이 왜 생기는지, 해결책은 없는지에 대해 공유해보려고 합니다 :) 서두가 길었죠? 관련 개념들부터 보면서 바로 시작해보겠습니다.
IME Composition
- IME (Input Method Editor) -사용자가 키보드 입력만으로 표현할 수 없는 문자를 입력할 수 있도록 도와주는 소프트웨어
- Composition - 입력한 문자를 조합하여 하나의 문자로 변환하는 과정
KeyboardEvent
- 사용자가 키보드를 입력할 때 발생하는 이벤트
- keyup, keydown, keypress(deprecated) 등
| event.code | 물리적 키 위치 (레이아웃 고정) | "KeyA", "Enter", "ArrowUp" |
| event.key | 실제 입력된 키 값 | "a", "Enter", "ArrowUp" |
| event.repeat | 키를 계속 누르고 있는지 | true (계속 눌림) / false (한 번 입력) |
| event.isComposing | IME 입력 중인지 여부 | true (조합 중) / false (확정됨) |
keydown vs. keyup vs. keypress
| keydown | keyup | keypress (deprecated) |
| 키가 눌렸을 때 (반복가능) | 키를 떼었을 때 (한번만 발생) | 문자를 입력했을 때 |
| 모든 키 | 모든 키 | 문자 입력을 위한 키 |
Composition Event
- 한글, 중국어 등 조합형 문자를 입력할 때 발생하는 조합 이벤트
- 영어와 같은 알파벳 입력에서는 발생하지 않음

- 이벤트 발생 순서 : compositionstart -> compositionupdate -> compositionend
let isComposing = false;
let currentInputValue = "";
// compositionstart: 한글 입력이 시작될 때 발생
$input?.addEventListener("compositionstart", (event) => {
console.log("Composition started");
isComposing = true; // 조합형 문자 입력 시작
});
// compositionupdate: 조합형 문자 입력 중간에 발생
$input?.addEventListener("compositionupdate", (event) => {
console.log("Composition updated:", event.data); // 입력 중인 조합형 문자를 출력
currentInputValue = event.data; // 입력 중인 값을 저장
});
// compositionend: 한글 입력이 끝나면 발생
$input?.addEventListener("compositionend", (event) => {
console.log("Composition ended");
isComposing = false; // 조합형 문자 입력 완료
currentInputValue = event.data; // 입력 완료된 값 저장
});
위와 같이 input에 eventListener를 추가하고, composition event가 발생할 때마다 추적해서 콘솔에 찍도록 하였더니 결과는 다음과 같습니다.
1️⃣ 한글

2️⃣ 영어

=> 한글은 composition event 발생, 영어는 발생하지 않음
자 그러면 이제 잘 알겠는데,, 그래서 왜 event 가 중복 발생이라는 거야? 싶으시겠죠?!
1️⃣ 한글
console.log(iscomposing , event.key)

1. 사용자가 한글을 입력할 때 조합 과정(compositionstart → compositionupdate → compositionend)이 발생
2. 이 과정에서는 keydown이 발생하지만, isComposing이 true
* keydown은 IME 조합 중에도 발생하기 때문에, 사진처럼 composition이 end하기 전 , 후로 enter에 대한 이벤트 중복 발생
2️⃣ 영어

- 키보드를 누르는 즉시 keydown 이벤트 발생
🙋 그럼 keypress로는 해결할 수 없나요?
있습니다. 단, 권장하지 않음
이유는
장치 의존적이기 때문 -> OS에서 어떻게 맵핑 되느냐에 따라 다르고, 입력 기계에 따라 다름

우선 저는 임시 방편으로 keypress로 해결했는데 mdn을 보니 이런 무시무시한 문구가 있더라구요 ㅎ.. 나중에 발견
🙋 왜 keypress로 했을 때 문제가 해결된 것 처럼 보였나요?
우선 keypress의 특징은 다음과 같습니다.
1. keypress는 문자 입력 키(영문, 숫자 등) 에서만 발생
2. 제어 키(Control Keys) (Enter, Shift, Esc 등)에서는 keypress가 발생하지 않음
$input?.addEventListener("keydown", async (event) => {
const keyboardEvent = event as KeyboardEvent;
if (keyboardEvent.key === "Enter") {
const inputValue = (event.target as HTMLInputElement).value;
showSkeleton(20, "section");
if (inputValue === "") {
alert("검색어를 입력해주세요.");
} else {
const searchResult = await movieService.getSearchResult(inputValue);
/// 중략
}
}
});
제 코드와 함께 보면 ,,
1. 사용자가 Enter 키를 누르면 keypress 이벤트가 실행됨
2. keyboardEvent.key === "Enter" 조건을 만족하면 API 요청 실행
3. 한글 입력 중이라도, Enter 키는 keypress 이벤트에서 감지되지 않기 때문에 IME 조합 중복 문제가 발생하지 않음
결론
keypress를 쓰지 말고 keydown, keyup 등으로 처리하되
isComposing의 값이 false일 때 (즉 조합이 완료되었을 때) 에 대한 조건을 넣어서 조합 중 event 발생하는 것 방지
$input?.addEventListener("keydown", async (event) => {
const keyboardEvent = event as KeyboardEvent;
console.log(event.isComposing, event.key);
if (keyboardEvent.key === "Enter" && event.isComposing === false) {
const inputValue = (event.target as HTMLInputElement).value;
showSkeleton(20, "section");
/// 위와 동일
영어랑 한글에 따라 이벤트 발생횟수가 달라지는 기이한 현상 덕분에 ,, keyboardevent와 IME에 대해서도 알게 되었습니다 :-)
여러분도 혹시나 keydown으로 했다면, 이런 현상은 없을지 점검해보시고 질문은 언제든 환영입니다 🤩
출처 :
https://w3c.github.io/uievents/#compositionevent
UI Events
Abstract This specification defines UI Events which extend the DOM Event objects defined in [DOM]. UI Events are those typically implemented by visual user agents for handling user interaction such as mouse and keyboard input. Status of this document This
w3c.github.io
https://developer.mozilla.org/ko/docs/Web/API/KeyboardEvent
KeyboardEvent - Web API | MDN
KeyboardEvent 객체는 키보드와 사용자의 상호 작용을 나타냅니다. 각 이벤트는 사용자와 키보드의 키(또는 보조 키를 같이 눌렀을 때의 결합)를 나타냅니다. 이벤트 타입 (keydown, keypress 또는 keyup)은
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event
Element: compositionstart event - Web APIs | MDN
The compositionstart event is fired when a text composition system such as an input method editor starts a new composition session.
developer.mozilla.org
'언어 영역' 카테고리의 다른 글
| [JS] 이벤트 전파 그리고 위임 (1) | 2025.09.19 |
|---|