개발 기록 남기기✍️

[에러일지] onKeyDown 이벤트 중복 실행 문제 본문

개발 일기장

[에러일지] onKeyDown 이벤트 중복 실행 문제

너해동물원 2023. 7. 27. 18:07

🚪 똑똑. 에러 왔습니다.

 

input에 검색어를 입력하면 추천 검색어 리스트가 나오고, 상하 방향키를 누를 때마다 포커스가 이동해야 한다.

그런데, 처음 방향키를 누를 때 이벤트핸들러가 두 번 호출되어 "건강"을 건너뛰고 "강아지"가 바로 포커싱 되는 문제가 발생했다.

 

검색을 해보니 한글 입력 시, 입력 중인 글자에 밑줄이 생기는데, 밑줄이 있는 상황에서 키보드 이벤트 발생 시 이벤트 핸들러가 두 번 호출된다고 한다. 한글의 경우 자음과 모음의 조합으로 한 음절이 만들어지는 조합 문자이기 때문에 글자가 조합 중인지, 조합이 끝난 상태인지를 알 수 없기 때문이다.

 

유독 한글에서만 이러한 문제가 발생되는건 IME 때문이다.

 

IME는 영어가 아닌 한글, 일본어, 중국어와 같은 언어를 다양한 브라우저에서 지원하도록 언어를 변환시켜주기 위한 OS 단계의 어플리케이션을 말한다. 그러나 IME 과정에서 keydown 이벤트가 발생하면, OS와 브라우저에서 해당 이벤트를 모두 처리하기 때문에 keydown 이벤트가 중복으로 발생하게 되는 것이다.

 

즉, IME를 통해 한글, 일본어, 중국어 등을 변환하는 과정(composition)에서 keydown 이벤트는 OS 뿐만 아니라 브라우저에서도 처리되기 때문에 중복 발생된다.

 

💡 해결 방법

키보드 이벤트 중 isComposing이라는 프로퍼티를 통해 이를 제어할 수 있다. 한글 등 비영어권 언어를 표현하는 과정에서 이 값을 참조하면 true 값을 반환한다.

 

방법 1) nativeEvent 사용

브라우저 고유 이벤트인 nativeEvent의 isComposing 프로퍼티에 접근하여 이벤트 핸들러를 제어할 수 있다.

const handleKeyArrow = (e) => {
    if (e.nativeEvent.isComposing) return;
	...
    // 실행할 로직
};

 

방법 2) 리액트의 SyntheticEvent 사용

리액트의 이벤트 핸들러는 크로스 브라우징의 일환으로 모든 플랫폼에서 동일한 이벤트 처리를 위해 구현된 인터페이스인 SyntheticEvent 객체를 사용한다. 이는 stopPropagation과 preventDefault 등을 포함하여 브라우저 고유 이벤트와 동일한 인터페이스를 가짐과 동시에 모든 브라우저에서 동일하게 동작하는 것을 보장한다.

 

리액트에서 제공하는 isCompositionStart, isCompositionEnd 이벤트를 통해 isComposing 상태를 제어하고, isComposing 상태에 따라 이벤트 핸들러를 제어할 수 있다.

const [isComposing, setIsComposing] = useState(false);

const handleKeyArrow = (e) => {
   if(isComposing) return;
   ...
   // 실행할 로직
};

return (
  <input 
    onKeyDown={handleKeyArrow}
    onCompositionStart={() => setIsComposing(true)}
    onCompositionEnd={() => setIsComposing(false)}
  />
  ...
);

nativeEvent를 통해 문제 해결

 

✏️ 마무리

분명 알고 있는 내용인데... 한동안 input 값에 따라 실시간으로 어떤 요청을 보낼 일이 없어서 useRef로 input 값 참조만 하다보니 isComposing 프로퍼티를 까맣게 잊고 있었다. (변명)

 

그래서 더 이상 까먹지 말자는 마음가짐으로... 부끄럽지만 소소한 에러 일지를 작성한다.

작성하면서 레퍼런스를 찾다보니 리액트의 합성 이벤트에 대한 이해도도 높아졌고, 왜 이벤트 핸들러의 타입을 선언할 때 React.MouseEvent 형식으로 작성해야 하는지도 알게 되었다!!

 

앞으로 또 까먹으면 나는 💩이다 진짜

 

 

📎 참고자료

https://handhand.tistory.com/287