본문 바로가기

Dev/[Vue.js]

[Vue.js] v-model로 한글을 처리하는 방법에 대하여

반응형

 

INTRO


 

vue.js에는 v-model이라는 양방향 바인딩 디렉티브가 있다.

v-model은 한국어/중국어/일본어를 입력할 경우, 즉시 바인딩 되지 않는 현상이 있다.

이를 처리하는 방법에 대해 정리해본다.

 

 


 

0. 서론

- 일반적인 웹 페이지에서는 입력 후 submit 버튼을 클릭한다거나 하는 별도의 추가 동작이 대부분 이어지며, input 엘리먼트에서 focus를 잃는 순간 정상 바인딩 되므로 v-model로 사용해도 무방한 경우가 많다.

- 그러나 화면에 즉각 반영되어야 할 필요가 있기에 이 글을 보고 계실것이다..

- 이 방법에 대해 고민해본 결과들을 정리해본다.

 

 

1. 대중적인 해결 방법

- 구글링해보면 아래와 같이 v-model 을 @input으로 대체하는 방법이 가장 많이 등장한다.

(typescript+setup 방식이므로, 기존과 다르게 보이는 부분이 있을 수 있으나 원리는 같다.)

- 결론은 아래와 같이 사용하면 큰 문제 없이 해결이 가능하다.

 

<template>
    <input
      type="text"
      @input="
         (e: Event) => {
          text =(e.target as HTMLInputElement).value
        }
      "
    />
    <h1>입력값 : {{ text }}</h1>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const text = ref('');
</script>


// 또는 

<template>
  <input type="text" @input="onChange" />
  <h1>입력값 : {{ text }}</h1>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const text = ref('');
const onChange = (e: Event) => {
  text.value = (e.target as HTMLInputElement).value;
};
</script>

- 그런데 뭔가 깔끔해보이지가 않는다.

- 분명 더 나이스한 방법이 있어보인다..

 

2. computed를 사용해본다면?

- computed를 사용하여도 밀림 현상을 해결할 수는 없다.

<template>
  <input type="text" v-model="text" />
  <h1>입력값 : {{ textAreaValue }}</h1>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';

const textAreaValue = ref('');

const onChange = (e: Event) => {
  textAreaValue.value = (e.target as HTMLInputElement).value;
};

const text = computed({
  get() {
    return textAreaValue.value;
  },
  set(val) {
    textAreaValue.value = val;
  },
});

- computed 는 이러한 케이스보다는, 캐싱이나 여러 값들을 연산한 결과를 만들 때 가장 효율적이라는것을 다시 한번 느꼈다..

 

 

3. custom directive 를 사용해본다면?

- custom directive를 사용한다면 해결할 수 있다.

(엄밀히 말하면 이 글의 주제인 v-model을 사용한 방법은 아니지만, 목적에는 부합한다고 본다.)

- 다만 문제가 있는데, vue3 버전에서는 양방향 바인딩을 지원하는 custom directive를 만들 수 있는지 없는지가 아직 확실하지 않다.

- 그 이유는 custom directive 를 사용할 때 Directive 객체의 메서드들을 구현해줘야하는데, 이 과정에서 3번째 파라미터로 vnode라는게 있다.

- vue2 에서는 vnode로 전달되는 값이 VNode2라는 객체였는데, vue3은 다른 객체가 넘어와 이 값을 변경하는 방법이 없음

- vue git repo의 pull request에서 이에 대한 논의가 아직 이뤄지고 있는 듯 하다.

 

https://github.com/vuejs/core/pull/3399#ref-commit-ed1c73f

 

types(defineComponent): support for expose component types by pikax · Pull Request #3399 · vuejs/core

fix #3367 Changes Support for typed Components - to allow extraction of the components available Support for typed Directives and ComponentCustomDirectives (aka global directives) Initial support ...

github.com

 

- any로 선언된 것들은 테스트용이므로 이해를 부탁드리며, addEventListener로 input이벤트를 감지하여 get/set을 하는 예제이다.

(실제 사용하시려면 unbind 메서드를 구현해서 EventListener 를 remove까지 해줘야 정상적인 코드이다.)

// main.ts

Vue.directive('two', {
  bind: function (el: HTMLInputElement | HTMLTextAreaElement, binding: DirectiveBinding<string>, vnode: any) {
    el.value = binding.value; // 초기값 설정
    const ev = (event: any) => {
      console.log(vnode);
      vnode.context[binding.expression as string] = event.target.value; // 값 업데이트
    };
    el.addEventListener('input', ev);
  },
  update: function (el: any, binding: any, vnode) {
    el.value = binding.value; // DOM 업데이트
  },
});
new Vue({
  render: (h) => h(App),
}).$mount('#app');


// App.vue
<template>
  <div>
    <input type="text" v-two="text1" />
    <h1>{{ text1 }}</h1>
  </div>
</template>
<script lang="ts">
export default {
  data() {
    return {
      text1: 'Hello world',
    };
  },
};
</script>

- 원하는대로 만들었지만, vue3에서는 확실치가 않다.

- 다른 방법으로 만들어야한다.

 

4. custom component를 만들어서 사용해본다면?

- custom component를 만들고, 해당 컴포넌트에 v-model 디렉티브를 사용하는 방법이 있다.

- 결론적으로 이 방법으로도 해결이 가능하며, 필자 기준으로는 이게 가장 깔끔해보인다.

(vue3에서 아직 양방향 custom directive를 지원하지 않는다는 전제하에..)

- 이 방법은 1번 해결방법을 활용하여 컴포넌트로 만들고, 이 컴포넌트에 props, emit을 사용하여 상위 컴포넌트와 데이터 바인딩이 가능하도록 한 예시이다.

// parent
<template>
  <customInput v-model="text" />
  <h1>입력값 : {{ text }}</h1>
</template>

<script setup lang="ts">
import customInput from '@/components/CustomInput.vue';
import { ref } from 'vue';
const text = ref('');
</script>


// child
<template>
  <input type="text" :value="props.modelValue" @input="updateValue" />
</template>

<script setup lang="ts">
const props = defineProps({
  modelValue: {
    type: [String, Number],
    default: '',
  },
});

const emit = defineEmits(['update:modelValue']);

const updateValue = (e: Event) => {
  const target = <HTMLInputElement>e.target;
  emit('update:modelValue', target.value);
};
</script>

 

 

 

마무리

결론적으로는 실제 프로젝트에서 4번의 형태로 사용중이며, 개발이나 유지보수 과정에서 큰 불편함을 느끼진 않는다.

그리고 3번 예시를 작성하기 위해 vue3 에서의 '양방향' custom directive를 한참이나 찾아 헤맸지만 결국 찾지 못해서 찝찝함이 남아있다..

국내/외 포스팅들도 전부 단방향만 예시를 들며 마치 "custom directive에 대한 모든 것을 정리했다!" 라는 식으로 작성한 글들이 많았다. 이 부분은 조금 더 공부를 해봐야 할 것 같다.

 

 

 

-퍼가실 때는 출처를 꼭 같이 적어서 올려주세요!

 

반응형