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에 대한 모든 것을 정리했다!" 라는 식으로 작성한 글들이 많았다. 이 부분은 조금 더 공부를 해봐야 할 것 같다.
-퍼가실 때는 출처를 꼭 같이 적어서 올려주세요!
'Dev > [Vue.js]' 카테고리의 다른 글
[Javascript+Vue.js] Scrollbar 여부에 반응하는 엘리먼트 만들기(with Class and Style Binding) (0) | 2023.03.02 |
---|---|
[Vue.js] vue 3.0 + Typescript 에서 vue-router적용기 (0) | 2022.06.13 |
[Vue.js] vue 2.0 에서 LifeCycle에 대한 삽질 후기 (Error in mounted hook) (0) | 2022.05.15 |
[vue.js] view에 바인딩 된 '배열' 변수를 업데이트 할 때, 감지 안되는 문제 (0) | 2022.05.12 |
[Vue.js] parent <-> child간 양방향 바인딩(with custom Tag) (0) | 2022.04.26 |