React, 한글 입력시 keydown 이벤트 중복 발생 현상

terry yoon·2022년 3월 31일
9
post-thumbnail

글을 작성한 이유

회사에서 autoComplete 컴포넌트를 만드는 과정에서 방향키를 이용해 자동완성 검색어를 탐색할 수 있도록 keydown 이벤트 핸들러를 등록한 적이 있다. 이 때, 영어가 아닌 한글로 입력한 후 keydown 이벤트를 실행할 때 이벤트가 중복으로 발생하였다. 문제의 원인을 찾고 이를 해결하는 과정을 공유하고자 한다.

문제 원인

IME composition

IME는 영어가 아닌 한글, 일본어, 중국어와 같은 언어를 다양한 브라우저에서 지원하도록 언어를 변환시켜주기 위한 OS 단계의 어플리케이션을 말한다.

그러나 IME 과정에서 keydown 이벤트가 발생하면, OS와 브라우저에서 해당 이벤트를 모두 처리하기 때문에 keydown 이벤트가 중복으로 발생하게 되는 것이다.

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

isComposing

이를 위해 Web API 스펙을 확인하며 event target에 KeyboardEvent.isComposing 이라는 프로퍼티를 제공한다. 자세한 설명을 보면, composition Session 중에 event가 발생하는지 여부를 불리언 값으로 반환한다고 명시되어 있다 (The KeyboardEvent.isComposing read-only property returns a boolean value indicating if the event is fired within a composition session)

즉, 한글 등 비영어권 언어를 표현하는 과정에서 이 값을 참조하면 true값을 반환한다.

해결 전략

isComposing 때 return

const keyboardEventHandler = (event) => {
 if(event.isComposing) return; 
  
  // ... 나머지 키보드 이벤트 
}

isComposing이 참인 순간은 아직 IME에 의한 composition 단계이므로, 이 단계에서 이벤트가 발생하지 않도록 하는 방법을 사용할 수 있다.

keycode 를 이용하는 방법도 있지만, keycode 는 deprecated 되었다고 하므로 사용을 지양하는 것이 좋겠다.

React 에서 해결 방법

isComposing 이 없는데...

하지만 리액트에서 keyboard event 객체를 살펴보면, isComposing이라는 프로퍼티를 제공하지 않는다. 방법을 찾다가, 아까 web API 문서에서 Composition Sesseion 다음에 이런 말이 나온다.

after compositionstart and before compositionend.

이 말을 즉, compoistion 단계의 시작과 끝을 위한 이벤트가 별도로 존재한다는 말이다. 찾아보니 react에서 composition event를 별도로 제공한다. (리액트 컴포지션 이벤트 )

onCompositionStart, onCompositionEnd

const [isComposing, setIsComposing] = useState(false); 
const handleClickEvent = (event) => {
  if(isComposing) return; 
  
  // 키보드 이벤트 ..
}

<AutoComplete
	onCompositionStart={()=>setIsComposing(true)}
    onCompositionEnd={()=>setIsComposing(false)}
    onKeydown={hanleClickEvent}
/>

이런 식으로 isComposing 상태를 별도로 관리하여, 각각의 composition 이벤트가 발생할 때 상태의 값을 변경하면 한글 입력에 따른 keydown 이벤트의 중복을 막을 수 있다.

보충 내용 (22.6.28)

댓글 내용을 보며, 에러 상황을 다시 보게 되었다. 현재 개발 환경이 타입스크립트와 리액트를 사용하고 있기 때문에, 엄밀히 isComposing을 참조할 수 없는건 타입 체커에 의한 에러였다.

즉 리액트 컴포넌트에 등록한 키보드 이벤트 객체 타입에 isComposing이 없기 때문에 타입 체커의 타입 검사 오류가 발생하여, 해당 속성을 사용할 수 없다고 생각했다. 하지만 아래 댓글처럼, 실제 키보드 이벤트는 결국 Web API에 정의된 것처럼 isComposing을 참조할 수 있다.

그럼 타입 에러를 해결하는 방법은..? 타입을 확장하는 방법이다.

인터페이스 보강(augment)

다음은 리액트의 키보드 이벤트 타입이다. 인터페이스로 타입이 정의되어 있기 때문에, 해당 타입을 확장하여 사용할 수 있다.

따라서 다음과 같이 타입을 확장하면 타입 에러가 발생하지 않을 것이다.

import { KeyboardEvent } from 'react'

interface KeyboardEvent {
  isComposing : boolean;
}


<input onKeydown={(event : KeyboardEvent) => {
	if(event.isComposing) return; 
  	...
}} />

다음과 같이 인터페이스 보강을 활용해, 타입 체커에 의한 에러를 해결할 수 있다고 생각한다.
(아직 확인해보지 않았다...)

profile
배운 것을 기록하는 FrontEnd Junior 입니다

2개의 댓글

comment-user-thumbnail
2022년 6월 24일

React typescript 환경에서도 keyboardEvent.nativeEvent.isComposing 와 같이 nativeEvent를 통해 접근해 isComposing 으로 예외처리가 가능합니다!
React.KeyboardEvent가 아닌 네이티브(빌트인이라고 하는게 맞을까요?🤔) KeyboardEvent에는 정의가 되어있어서요.
React.KeyboardEvent도 타입 정의만 안되어있고 런타임에서는 isComposing을 갖고 있어서 (event: any) => {...}와 같이 any로 받아서 쓰면 쓸수는 있더라구요. 바람직 하진 않은 것 같습니다ㅎㅎ
React 17, Typescript 4.7 기준입니다! 잘 읽고 가요~

1개의 답글