Composition Event

GwangSoo·2025년 5월 4일
0

개인공부

목록 보기
24/37
post-thumbnail

프로젝트를 진행하다가 이상한 버그 하나를 발견했다.

실패 영상

위 영상은 버그를 재현한 결과인데, 입력(input)에 한글을 입력한 후 엔터를 치면 마지막 글자가 한 번 더 추가되는 것을 확인할 수 있다.

이 현상에 대해 찾아보니 Composition Event 때문이라는 것을 알게 되었다.

Composition Event란?

Composition Event란 유저가 IME(Input Method Editor, 입력기)를 통해 조합형 문자를 입력할 때 발생하는 이벤트다. 주로 한국어, 일본어, 중국어처럼 자음과 모음이 합쳐져 완성형 글자가 되는 언어에서 사용되며, 리액트에서는 onCompositionStart, onCompositionUpdate, onCompositionEnd로 다룰 수 있다.

그렇다면 이 이벤트가 어떻게 위 상황에 영향을 미치는 걸까? 이를 이해하려면 Composition Event의 동작 과정을 알아야 한다.

이제부터 "테스트" 입력을 예시로 들며 compositionstart, compositionupdate, compositionend에 대해서 설명하겠다.

compositionstart

IME가 조합 버퍼를 열 때 딱 한 번 발생한다.

즉, "ㅌ" 입력이 시작되는 시점, "텟"에서 "테스"로 넘어가는 시점, "테슽"에서 "테스트"로 넘어가는 시점에 각각 발생한다.

compositionupdate

자음, 모음 키를 누를 때마다 IME가 내부 버퍼(조합 중인 문자열)를 갱신할 때 발생한다.

즉, "ㅌ"에서 "테", "텟"으로 가는 과정처럼 글자가 조합되는 과정에서 발생한다.

이때 e.data에는 "ㅌ", "테", "텟"처럼 중간에 바뀐 전체 조합 문자열이 담긴다.

compositionend

조합을 확정하거나(Enter/Space/구두점/blur 등) 취소(Esc)할 때 발생한다.

이 시점이 지나야만 onChange를 통해 실제 value가 최종적으로 커밋된다.

마지막 글자가 한 번 더 추가되는 이유

Composition Event에 대해서 이해해보았으니, 이제 왜 마지막 한 글자가 추가되는지 알아보자. 재현에 사용한 코드는 다음과 같다.

"use client";

import { useState } from "react";

export default function Home() {
  const [value, setValue] = useState("");
  const [todos, setTodos] = useState<string[]>([]);

  const onAddTodo = () => {
    setTodos([...todos, value]);
    setValue("");
  };

  const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter") {
      console.log(
        "Composition",
        e.nativeEvent.isComposing,
        "Current Value",
        value
      );
      onAddTodo();
    }
  };

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };

  return (
    <div>
      <div>
        <input value={value} onChange={onChange} onKeyDown={onKeyDown} />
        <button onClick={onAddTodo}>Add</button>
      </div>
      <hr />
      {todos.map((todo, index) => (
        <p key={`${todo}-${index}`}>{todo}</p>
      ))}
    </div>
  );
}

이제 동작에 대해 자세히 살펴보자. Enter 키를 누르면 onKeyDown두 번 호출되면서 마지막 글자가 추가된다.

  1. 첫 번째 keydown (isComposing === true)
    • IME에 조합 확정 신호를 보내는 단계
    • 이 시점의 value는 아직 "테스트" 전체 (조합 전 상태)
    • 따라서 onAddTodo()가 호출되면 "테스트"가 todos에 추가되고 value는 빈 문자열이 됨
  2. IME 버퍼("트") → flush → beforeinputinputonChange
    • IME가 내부 버퍼에 있던 마지막 음절 "트"만 DOM에 커밋
    • React onChangestate.value가 "트"로 갱신됨
  3. 두 번째 keydown (isComposing === false)
    • 조합이 끝난 후 실제 Enter 이벤트로 처리되는 단계
    • 이미 value가 "트"로 반영된 상태
    • 다시 onAddTodo()가 호출되어 "트"가 추가됨

그래서 마지막에 "트" 글자가 한 번 더 추가되는 현상이 발생한 것이다.

해결 방법

해결 방법은 간단하다. onKeyDown 부분에서 e.nativeEvent.isComposingfalse일 때만 onAddTodo()를 호출하면 된다.

성공 영상

후기

이번 경험으로 Composition Event라는 것에 대해 알게 되었으며, 앞으로 입력 제출 관련 처리는 onKeyDown보다 form 태그를 우선 활용해야겠다고 생각하게 되었다.

참고

0개의 댓글