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번 등록되는 이슈가 사라졌다

    반응형

    댓글