onKeyDown, onChange를 사용할때 한글 조합 문제

반응형

input에 입력후 Enter를 눌렀을때 string 배열에 입력값을 push해서 보여지는 UI를 만들었다
영어를 입력했을땐 문제가 없었으나, 한글을 입력하고 엔터를 누르면 끝 글자만 떨어져나와 2개씩 string이 생성되는 문제가 발생하게 되었다
사실 늘 이런 컴포너는트는 mantine을 쓰든 뭘 쓰든 대부분 UI 라이브러리를 사용해서 가져다 쓰기만했었지 직접 만드는건 처음이게 이런 문제가 발생할 수 있다는걸 모르고 있었다
그래서 keyUp으로 뭔갈 해야해야하나? 지연을 줘서 늦게 input을 리셋 시켜야하나? 고민하다가 chatGPT한테 물어보니 원인을 알 수 있었다

<div className="w-full px-4 rounded border border-gray-300 text-sm min-h-[30px] py-2 placeholder:text-sm placeholder:text-gray-400">
  {values.map((v, idx) => (
    <span
      key={`${v}-${idx}`}
      className="px-2 py-[2px] inline-flex items-center bg-[#EFEFEF] rounded-full mr-1 mb-2"
    >
      <span className="text-sm">{v}</span>
      <button type="button" className="w-4">
        <XMarkIcon />
      </button>
    </span>
  ))}
  <input
    type="text"
    placeholder={props.placeholder}
    className="w-full outline-none"
    value={inputValue}
    onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
      if (e.key === "Enter") {
        e.preventDefault();

        const value = e.currentTarget.value.trim();
        if (value) {
          setValues((prev) => [...prev, value]);
          setInputValue("");
        }
        console.log(e.currentTarget.value);
      }
    }}
    onChange={(e) => {
      const value = e.target.value;
      setInputValue(value);
    }}
  />
</div>

기존에는 이렇게 작성 했고 GPT가 내놓은 답은 아래와 같다

function MultiSelect({ ...props }: IInputProps) {
  const [inputValue, setInputValue] = useState<string>("");
  const [values, setValues] = useState<string[]>([]);
  const [isComposing, setIsComposing] = useState(false); // 한글 조합 상태 확인

  return (
    <div className="w-full px-4 rounded border border-gray-300 text-sm min-h-[30px] py-2 placeholder:text-sm placeholder:text-gray-400">
      {values.map((v, idx) => (
        <span
          key={`${v}-${idx}`}
          className="px-2 py-[2px] inline-flex items-center bg-[#EFEFEF] rounded-full mr-1 mb-2"
        >
          <span className="text-sm">{v}</span>
          <button type="button" className="w-4">
            <XMarkIcon />
          </button>
        </span>
      ))}
      <input
        type="text"
        placeholder={props.placeholder}
        className="w-full outline-none"
        value={inputValue}
        onCompositionStart={() => setIsComposing(true)}
        onCompositionEnd={() => setIsComposing(false)}
        onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
          if (e.key === "Enter" && !isComposing) {
            e.preventDefault();

            const value = e.currentTarget.value.trim();
            if (value) {
              setValues((prev) => [...prev, value]);
              setInputValue(""); // 더 깔끔한 방식
            }
          }
        }}
        onChange={(e) => setInputValue(e.target.value)}
      />
    </div>
  );
}

conpositionstart, compositionend를 활용한 방법이다. 이런 이벤트도 지원해주는지 몰랐다 ^_ㅠ...
두 이벤트는 조합을 활용하는 언어들의 조합 시작과 끝을 추적해준다

  • conpositionstart : 조합 시작, 조합중인 문자열을 알 수 있음
  • conpositionend : 조합 끝. 조합이 끝난 문자열을 알 수 있음

왜 영어를 입력할때랑 한글을 입력할때랑 다를까?

영어는 키보드를 누를때마다 바로바로 문자 하나씩 입력되고 끝난다. 조합이란게 없는 언어라서 onKeyDown, onChange, onKeyUp 등 이벤트가 있는 그대로 단순하게 작동한다.

근데 영어와 달리 한글은 IME(입력기)를 통해 문자를 조합하는 툭수한 입력 방식이다. 이 과정은 브라우저가 처리하는게 아니라 운영체제의 IME가 중간에서 조합 상태를 관리해준다고 한다

그렇다보니, onKeyDown, onChange 이벤트 같은 경우 한글이 아직 조합중인데 발생할 수 있다. 우리는 문자열 1개가 조합 되어야하지 입력을 완료(?)했다고 볼 수 있다보니 이벤트 발동 타이밍이 불명확 해지는 것이다. Enter도 조합중에 누르면 확정 + 키다운이 동시에 발생할 수 있는 것이다.

그래서 "안녕" 입력후 Enter를 누를때 "안녕" 1개, "녕" 1개 2번 발생하는 과정을 보면

  1. 조합 도중 "안녕"이 value로 보이게 됨
  2. 그 상태에서 Enter 누르면
  3. 조합이 끝나기 전에 onKeyDown 실헹 -> "안녕"이 추가
  4. 이어서 조합이 확정되며 onKeyDown이 다시 한번 더 실행 -> "녕"만 따로 value로 들어와 또 추가됨

이렇게 되는 것이다
이게 다 조합중이다보니 키보드 관련 타이밍이 꼬이는것이다

아무튼, GPT가 제시해준 방법대로 고치니 이젠 2번 등록되는 이슈가 사라졌다

반응형

댓글