정의
한글로 "안녕"을 치는 도중, 아직 "ㅇ ㅏ ㄴ"처럼 조합이 끝나지 않은 상태에서 화면이 한 번 다시 그려지면 글자가 깨지거나 두 번 찍히는 경험을 한 적이 있을 것이다. 이 개념은 직접 만든 에디터에서 그 깨짐을 막고 싶을 때 쓴다.
IME(Input Method Editor, 여러 키 입력을 조합해 한 글자를 만드는 입력기)의 조합은 한글·일본어·중국어처럼 여러 키 입력이 하나의 문자로 확정되기 전까지 브라우저/OS가 유지하는 native composition session(글자가 확정되기 전의 "임시 입력 세션")이다. contenteditable(사용자가 직접 글자를 편집할 수 있게 만든 HTML 요소) 기반 리치텍스트 에디터에서 조합 중 DOM 교체·React re-render·serialize commit(에디터 내용을 문자열/데이터로 저장하는 과정)이 끼어들면 세션이 끊기고, 조합 중 문자가 중복·유실·조기 확정된다.
왜 필요한가
웹 에디터는 “키 입력 → state 갱신 → DOM 반영” 루프가 빠르다. IME는 그 루프 중간에 “아직 확정되지 않은 문자열”을 유지해야 한다. 조합 중 부모가 guest value를 prop으로 덮어쓰거나, blur/unmount 경로가 commit을 호출하면 브라우저 underlines·조합 버퍼가 리셋된다. 커스텀 compositionstart/end 이벤트를 새로 설계하기 전에, 사용 중인 에디터가 composing 플래그를 이미 제공하는지 확인하는 것이 비용 대비 효과가 크다.
동작 원리
- 사용자가 IME로 입력을 시작하면 브라우저가 composition session을 연다(underline 표시).
beforeinput/input이벤트는 조합 중·조합 후 단계가 다르게 온다.- 에디터는
view.composing(ProseMirror) 또는nativeEvent.isComposing같은 신호로 “지금은 flush 금지”를 판단한다. - 조합이
compositionend로 확정되면 그때 commit/serialize가 안전하다. - React guest 패턴에서는 mount-once initial content로 부모 re-render가 composing 텍스트를 clobber하지 않게 한다.
| 단계 | 위험 동작 | 안전 패턴 |
|---|---|---|
| 조합 중 | blur commit, plugin menu open | composing gate |
| 조합 중 | parent prop으로 value replace | mount-once guest |
| 검증 | jsdom keyboard only | CDP imeSetComposition |
실무 적용
Playwright(Chromium)에서는 DevTools Protocol Input.imeSetComposition으로 조합 단계를 순차 재현하고, Input.insertText로 확정한다. jsdom·happy-dom은 composition lifecycle을 충실히 흉내 내지 않으므로 IME 회귀의 유일한 근거가 되기 어렵다.
트레이드오프
- composing defer는 조합 중 구조 변경(split/merge) 을 지연시킬 수 있다 — 대부분 수 ms~수백 ms로 UX상 허용된다.
- mount-once guest는 “부모가 강제로 content reset” 패턴과 충돌한다 — reset이 필요하면 keyed remount로 명시적 재시작.
- Safari/WebKit과 Chromium의 composition 타이밍 차이는 환경마다 다를 수 있어, 크로스브라우저 E2E가 안전하다.
사용하면 안 되는 경우
- 단일 언어(ASCII only) 내부 도구에서 IME를 전혀 지원하지 않는다고 명시적으로 범위를 제한한 경우(그래도 모바일 OS keyboard는 IME 경로를 탈 수 있음).
- composition 이벤트만 추가하고 commit 게이트는 그대로 두는 경우 — 이벤트 핸들러만으로는 flush 경로 전체를 막기 어렵다.
흔한 실수
- “유닛 테스트 green이니 IME OK” — jsdom은 composition·좌표 API가 불완전하다.
- slash menu·mention plugin이
view.update마다 setState — 조합 중에도 부모 re-render. - blur/unmount/keyboard delegate마다 commit 복제 — composing 체크 누락.
관련 개념
- focused-guest-editor — guest commit 경계·mount-once
- contenteditable-keyboard-history-delegation — focus 중 키·히스토리 상호작용