리엑트 앱에서 한글을 입력 후 keydown 이벤트로 엔터를 입력받았는데, 한글이 중복입력되는 문제가 생겼다.
왜 이런 문제가 생겼는지 알아보자.
내가 진행하고 있는 프로젝트에서 텍스트를 입력 후 엔터를 통해 입력한 텍스트를 리스트 아이템에 추가 할 일이 생겼다.
나는 추가적인 버튼을 넣지 않는 깔끔한 UI를 원했다.
Keydown 이벤트를 사용하지 않고 form 태그로 감싸고 onSubmit 핸들러를 달아주는 방법도 고려했지만 이 컴포넌트에서 즉시 서버로 요청을 보내는 것이 아니기 때문에 고려 사항에서 제외했다.
그래서 엔터인 키보드 이벤트가 발생하면 리스트에 아이템을 추가하는 핸들러를 작성하였다.
const handleSkillEnter = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
// useState 대신 reducer를 사용하였습니다. 포스트의 핵심과 관련 없습니다.
dispatch({ type: 'INPUT', payload: { inputType: 'skills', value: skill } });
setSkill('');
}
};
그런데 이런식으로 작성했을 때, 엔터를 입력받을 시 리스트에 아이템이 2번씩 들어가는 현상이 발생하였다.
하지만, 영어를 입력하서 엔터를 했을 때는 의도한 대로 아이템이 하나만 추가되었다.
문제 원인
이렇게 한글이 중복으로 들어간 원인은 IME(Input Method Editor) 때문이다.
IME란?
IME는 일반 키보드에서 쉽게 표현할 수 없는 언어로 텍스트를 입력할 수 있도록 하는 OS레벨의 입력 에디터이다.
한글을 예로 들자면 한글은 하나의 글자를 이루기 위해 자음 + 모음 그리고 추가적인 받침이 필요하다.
여기서 IME는 이 자음, 모음을 입력받아 하나의 글자로 composition(합성)을 해준다.
예를 들어 내가 "리엑트"를 입력했을 때 "리엑"은 composition이 완료되었지만 '트'는 composing 상태일 수 있다.
이 때 엔터를 이벤트 핸들러가 2번 호출되어 리스트에 "리엑트"가 추가되고, composing 상태인 '트'가 추가적으로 리스트에 들어간다.
그렇다면 글자가 composing중인지 아닌지 확인하며 중복 입력을 막을 수 있지 않을까?
해결법
첫번째 해결법
input에 setIsComposing을 콜백함수로 주어서 isComposing 플레그를 만들었다.
keydown이벤트 핸들러는 composing 상태에서는 return 한다.
const [isComposing, setIsComposing] = useState(false)
const handleSkillEnter = (e: KeyboardEvent<HTMLInputElement>) => {
if (isComposing) return;
if (e.key === 'Enter') {
dispatch({ type: 'INPUT', payload: { inputType: 'skills', value: userSkillInput } });
setUserSkillInput('');
}
}
return <input
onCompositionStart={()=>setIsComposing(true)}
onCompositionEnd={()=>setIsComposing(false)}
onKeydown={handleSkillEnter}
/>
두번째 해결법
플레그를 없에고 이벤트의 nativeEvent에서 isCompsing이 true이면 리턴을 하여 중복을 막았다.
const handleSkillEnter = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.nativeEvent.isComposing) return;
if (e.key === 'Enter') {
dispatch({ type: 'INPUT', payload: { inputType: 'skills', value: userSkillInput } });
setUserSkillInput('');
}
}
return <input onKeydown={handleSkillEnter}/>