v-model의 IME 입력방식 이슈와 custom input mask

13070 단어 vueIMEvue3tsv modelIME

input masking을 라이브러리를 사용않고 구현하다가 한글입력시 키보드 입력에 바로바로 v-model값이 업데이트가 안되는 문제를 발견했다.

문제 발견 전: v-model과 keyup으로 구현된 마스킹

H:MM:SS 형식의 마스킹을 원했기 때문에 keyup 이벤트가 발생할 때 현재 value의 형식에 따라 :을 붙이고 숫자나 콜론이 아닐 경우는 입력을 원복 시키는 방식으로 구현

<!-- template -->
<input type="text" v-model="hour" maxlength="7" placeholder="H:MM:SS" @keyup="maskingInput"/>
//script
maskingInput(event: KeyboardEvent): void {
  if (event.key !== "Backspace") {
    if(this.hour) {
      if(!this.hour.match(/[0-9:]/g)) this.hour = '';
      if(this.hour.match(/^\d{1]$/)) this.hour += ':';
      if(this.hour.match(/^\d{1}:\d{2}$/)) this.hour += ':';
    }
  }
}

하지만 한글 입력시 v-model이 업데이트가 되지않아 원하는 대로 구현이 불가능했다.

찾아보니 공식문서에서 IME 입력방식, 즉 한글, 일어, 중국어 등의 일종의 문자를 조합하는 방식의 언어는 IME composition이 일어나는 동안 v-model의 업데이트가 일어나지 않는다고 한다. 대신 input이벤트를 활용해 즉시 값을 넣어주는 방식을 권하고 있다.

컴포징이 일어날때도 keyup/down 이벤트는 일어나기 때문에 maskingInput()을 keyup에 그대로 사용하되 input리스너만 붙여 잘 업데이트 시켜주면 될 것 같았다.

두번째 시도: input리스너로 바로바로 value를 업데이트 하되 keyup에 그대로 함수 사용

++ 달라진 부분
1) this.hour의 값을 토대로 포맷에 부합하는지 확인하기 보다
key값을 갖고 판단해 값을 아예 미리 빈문자로 업데이트

2) 콜론을 붙이는 방식도 글자수에 따라 숫자 입력시와 backspace로 제거시에 콜론을 사이에 넣는 방식으로 변경해 더 자유롭게

<!-- template -->
<input type="text" :value="hour" maxlength="7" placeholder="H:MM:SS" @input="updateInput" @keyup="maskingInput"/>
//script
updateInput(event: InputEvnet): void {
  const inputNode = event.target;
  this.hour = inputNode.value;
},
maskingInput(event: KeyboardEvent): void {
  if (!event.key.match(/[0-9:]/g)) this.hour = "";
  else {
    const masked = this.hour.replace(/:/g, "");
    if (this.hour.length === 3) this.hour = masked.slice(0, 1) + ":" + masked.slice(1);
...
  }
}

이렇게 컴포징 도중에도 input에 바로 값 적용은 가능해졌지만 크롬에서 input이벤트가 두번 발생하는 문제가 발견됐다(사파리는 정상 작동).

원하는 값 이외의 값을 넣고 다시 hour값이 초기화 된 후 또 입력할 경우 아래와 같이 이전에 들어간 값 한번, 이전값과 컴포징중인 값이 같이 한번, 총 두번의 이벤트가 일어났다.

좀 더 보니 input이벤트 뿐만 아니라 input 태그에서 일어나는 다른 이벤트들도 IME방식에선 두번씩 일어나는 것 같다. 문자 입력후 입력창에서 포커스를 벗어난뒤 다시 입력한다면 한번씩만 일어나지만 완전한 해결방법은 아직 없는듯 하다. 관련 이슈

마지막 시도: 크롬 대응

위와 같은 문제로 인해 크롬에서 값이 삭제된 후 다시 숫자를 넣으면 이전에 컴포징 된 값 + 숫자로 들어와 현재 키의 값을 기준으로 포맷 부합 여부를 판단하는 로직으로는 제대로 된 동작을 하지 못했다.

따라서 포맷에 부합되는 키 입력시에도 이전에 컴포징 된 값을 한번 더 지워줘는 추가 처리가 필요해졌다.

//script
maskingInput(event: KeyboardEvent): void {
   if (!event.key.match(/[0-9:]/g)) ...
   else {
    this.hour = this.hour.replace(/[^0-9:]/g, '');
    ...
   }
}

마지막으로 59(분/초)까지만 받는 처리는 input mask의 역할로 보기엔 어려워 입력은 99까지 그대로 받는 대신 api 날리기전에 확인하는 방식으로 처리했다.

완성된 모습

결론

1) IME 방식은 v-model이 제대로 작동하지 않으니 input이벤트로 직접 업데이트 해줄것
2) IME 방식은 input에서 이벤트가 중첩 발생하기 때문에 대응 필요(chrome 한정)

좋은 웹페이지 즐겨찾기