keyDown, keyUp 이벤트 2번 실행

이현섭·2023년 5월 8일
4
post-thumbnail

회사 프로젝트에서 자동완성 관련 코드를 작성하던 도중 keyDown 이벤트가 2번 발생하는 상황을 겪었습니다. 이와 관련한 경험을 공유하고자 글을 작성합니다.

  1. input 태그에 value를 입력하면 검색 결과가 아래 리스트로 나옵니다.
  2. keyDown 이벤트를 활용해서 화살표 아래 버튼을 누르면 index가 바뀌며 검색목록이 선택됨이 보여야합니다.

    const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {

        switch (event.key) {
            case "ArrowDown":
                setSelectedIndex((prev) =>
                prev >= searchList.length - 1 ? 0 : prev + 1
                );
                break;

            case "ArrowUp":
                setSelectedIndex((prev) =>
                prev <= 0 ? searchList.length - 1 : prev - 1
                );
                break;
                
            default:
                break;
        }

    };

원인은 한글과 영어의 차이였습니다.

한글은 자음과 모음의 조합으로 만들어지기 때문에 조합문자인데, 이에 반해 영어는 조합 문자가 아닙니다.
영어와 달리 한글을 입력하면 입력 중인 글자 바로 아래에 검은 밑줄이 생기는데, 이는 조합중임을 나타냅니다. 조합이 완료되면 밑줄이 사라지고, 완성된 글자가 입력됩니다. 이러한 기능을 '한글 조합 입력기' 라고 합니다.

한글 입력기는 기본적으로 조합형 입력방식을 사용합니다. 이 방식은 초성, 중성, 종성 등을 조합하여 글자를 완성하는 방식입니다. 따라서 사용자가 한글 글자를 입력하기 위해서는 여러 개의 키 입력이 필요합니다.

예를 들어, '한' 이라는 글자를 입력하려면 "ㅎ", "ㅏ", "ㄴ"의 3 개의 자소를 순서대로 입력해야 합니다. 이때 각각의 자소 입력은 onkeydown 이벤트로 감지됩니다.

그러나 한글 입력기는 사용자가 모든 자소를 입력하고나서야 실제로 완성된 글자를 컴퓨터에 전달합니다.
따라서 모든 자소 입력에 대해 onkeydown 이벤트가 발생하고, 실제로 완성된 글자가 전달되는 시점에서 한 번 더 onkeydown 이벤트가 발생하는 것입니다.

이러한 이유로 한글 입력상태에서는 onkeydown 이벤트가 2번 발생하는 것이 일반적입니다.

해결 방법은?

1. KeyboardEvent.isComposing

'KeyboardEvent.isComposing' 은 자판 입력 도중 조합 중 이라는것을 나타내는 'Boolean' 값입니다.

React에서 이벤트 핸들러의 'event' 객체는 'SyntheticEvent' 객체로 래핑되어 제공됩니다. 이는 브라우저마다 다른 이벤트 객체를 통일된 방식으로 사용하기 위해 만들어진 것입니다. 그러나 'isComposiong' 프로퍼티는 'SyntheticEvent' 에는 없으므로 React에서는 직접 접근할 수 없습니다. 대신 'event.nativeEvent' 프로퍼티를 사용하여 네이티브 이벤트 객체에 직접 접근해야합니다.

하지만 이 방법은 리액트의 가상 돔(Virtual DOM) 을 우회하게 되어 성능 저하를 야기할 수 있으므로 가능하면 사용하지 않는 것이 좋습니다.

    const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {

      	// 추가된 코드
        if(event.nativeEvent.isComposing) return;

        switch (event.key) {
            case "ArrowDown":
                event.preventDefault();
                setSelectedIndex((prev) =>
                prev >= searchList.length - 1 ? 0 : prev + 1
                );
                break;

            case "ArrowUp":
                setSelectedIndex((prev) =>
                prev <= 0 ? searchList.length - 1 : prev - 1
                );
                break;
                
            default:
                break;
        }

    };

2. IME 이벤트 사용

한글 입력 처리와 관련된 이벤트 중에는 IME(Input Method Editor) 이벤트가 있습니다. 이 이벤트를 사용하여 한글 입력 처리를 제어할 수 있습니다. IME 이벤트는 키보드 입력 외에도 마우스 입력 등 다양한 입력을 처리할 수 있습니다.

IME(Input Method Event)는 키보드 입력에 대한 처리를 담당하는 시스템 소프트웨어입니다.
IME 이벤트는 키보드 입력과 관련하여 발생하는 이벤트 중 하나입니다.

IME 이벤트는 주로 한글, 중국어, 일본어 등의 복합어와 문자 조합을 처리할 때 사용됩니다. 예를 들어, 한글 입력 시 "ㅎ" + "ㅏ" 조합을 처리할 때는 실제로 "하" 가 입력되기 전에 "ㅎ" 과 "ㅏ" 를 조합하도록 하기 위해 IME 이벤트가 발생합니다.

React 에서 IME 이벤트를 다루기 위해서는 'onCompositionStart', 'onCompositionUpdate', 'onCompositionEnd' 와 같은 이벤트를 사용할 수 있습니다.

function handleComposition(event) {
  if (event.type === "compositionend") {
    // 한글 입력이 끝난 경우 처리할 코드
    console.log("한글 입력 끝");
  } else {
    // 한글 입력 중인 경우 처리할 코드
    console.log("한글 입력 중");
  }
}

<input type="text" onCompositionStart={handleComposition} onCompositionUpdate={handleComposition} onCompositionEnd={handleComposition} />

이 코드는 'input' 요소에 IME 이벤트 핸들러를 등록하는 예시입니다. 'onCompositionStart', 'onCompositionUpdate', 'onCompositionEnd' 이벤트가 발생할 때마다 'handleComposition' 함수가 호출됩니다. 함수 내부에서는 'event.type' 을 사용하여 IME 이벤트 종류를 확인하고, 필요한 처리를 수행합니다.

// 수정한 코드

const handleCompositionStart = (event: React.CompositionEvent<HTMLInputElement>) => {
  setIsComposing(true);
};

const handleCompositionEnd = (event: React.CompositionEvent<HTMLInputElement>) => {
  setIsComposing(false);
};

const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
  if (isComposing) {
    return;
  }

  switch (event.key) {
    case "ArrowDown":
      event.preventDefault();
      setSelectedIndex((prev) => (prev >= searchList.length - 1 ? 0 : prev + 1));
      break;

    case "ArrowUp":
      setSelectedIndex((prev) => (prev <= 0 ? searchList.length - 1 : prev - 1));
      break;

    default:
      break;
  }
};

return (
  <input
    value={inputValue}
    onChange={handleInputChange}
    onKeyDown={handleKeyDown}
    onCompositionStart={handleCompositionStart}
    onCompositionEnd={handleCompositionEnd}
  />
);

여기서 handleCompositionStart, handleCompositionEnd 함수는 IME 이벤트가 시작되고 종료될 때 호출됩니다. 'isComposing' 상태를 설정하고, 'handleKeyDown' 함수에서 이 값이 'true' 인 경우에는 이벤트를 무시합니다. 이렇게 함으로써, IME 이벤트 처리에 대한 문제를 해결할 수 있습니다.

profile
안녕하세요. 프론트엔드 개발자 이현섭입니다.

0개의 댓글