vue3的setup語法如何自定義v-model為公用hooks
前言
- 基礎(chǔ)篇:簡單介紹
vue3的setup語法如何自定義v-model; - 進階篇:如何提取
v-model語法作為一個公用hooks;
基礎(chǔ)
基礎(chǔ)篇可繞過,只是對于官網(wǎng)給出的教程,進行了總結(jié)概括并給出demo
基本的v-model
子組件中滿足兩個點,即可完成自定義雙向綁定:
props中定義一個值xxxemit中定義一個update:xxx事件
下面我們來寫一個最基本的v-model組件:
props中定義一個modelValue值,并綁定到input的value屬性上;emit中定義一個update:modelValue事件
需要注意的是,當modelValue作為props傳入,update:modelValue事件將被自動注冊到emit事件中
<template>
??<input
????type="text"
????@input="emit('update:modelValue',?$event.target.value)"
????:value="props.modelValue"
??/>
</template>
<script?setup>
const?emit?=?defineEmits();
const?props?=?defineProps({
??modelValue:?String,
});
</script>父組件中,引入modelComp子組件,并綁定test值到v-model上,test便完成了一次雙向綁定。
<template>
??<modelComp?v-model="test"></modelComp>
</template>
<script?setup>
import?{?ref,?watch?}?from?"vue";
import?modelComp?from?"./components/model/modelComp.vue";
const?test?=?ref("");
</script>這便是一個最基本的自定義v-model組件;
多個v-model綁定
當我們需要多個雙向綁定時,如下:
<modelComp
??v-model="test"
??v-model:test1="test1"
??v-model:test2="test2"
></modelComp>
<script?setup>
import?{?ref,?watch?}?from?"vue";
import?modelComp?from?"./components/model/modelComp.vue";
const?test?=?ref("");
const?test1?=?ref("");
const?test2?=?ref("");
</script>子組件中,同樣按著兩個點來定義:
props中定義兩個值,test1和test2emits中定義兩個事件,update:test1和update:test2
<template>
??<input
????type="text"
????@input="emit('update:modelValue',?$event.target.value)"
????:value="props.modelValue"
??/>
??<input
????type="text"
????@input="emit('update:test1',?$event.target.value)"
????:value="props.test1"
??/>
??<input
????type="text"
????@input="emit('update:test2',?$event.target.value)"
????:value="props.test2"
??/>
</template>
<script?setup>
const?emit?=?defineEmits(["update:modelValue","update:test1",?"update:test2"]);
const?props?=?defineProps({
??modelValue:?String,
??test1:?String,
??test2:?String,
});
</script>v-model修飾符
vue提供了一些v-model修飾符,我們可以在v-model中使用他們:
<modelComp ??v-model.trim="test" ??v-model:test1.lazy="test1" ??v-model:test2.trim.lazy="test2" ></modelComp>
在一些場景下,我們需要自己定義修飾符,來滿足我們的需求,舉個栗子:
<modelComp ??v-model.a="test" ??v-model:test1.b.c="test1" ></modelComp>
默認v-model中我們綁定了a修飾符,v-model:test1中則綁定b和c兩個修飾符;
對于修飾符,我們需要滿足以下條件:
- 對于默認
v-model來說,需要props中定義兩個值modelValuemodelModifiers,接受修飾符key值
- 對于自定義
v-model:xxx來說,props中:xxxxxxModeifiers,接受修飾符key值
由此,上代碼:
<template>
??<input?type="text"?@input="vModelInput"?:value="props.modelValue"?/>
??<input?type="text"?@input="vModelTest1"?:value="props.test1"?/>
</template>
<script?setup>
const?emit?=?defineEmits(["update:modelValue",?"update:test1"]);
const?props?=?defineProps({
??modelValue:?String,
??//接受v-model的修飾符
??modelModifiers:?{
????default:?()?=>?({}),
??},
??test1:?String,
??//接受v-model:test1的修飾符
??test1Modifiers:?{
????default:?()?=>?({}),
??}
});
const?vModelInput?=?(e)?=>?{
??let?value?=?e.target.value
??console.log(props.modelModifiers);
??//{a:true}
??if(props.modelModifiers.a){
??????//處理value值
??}
??emit("update:modelValue",?value);
};
const?vModelTest1?=?(e)?=>?{
??let?value?=?e.target.value
??console.log(props.test1Modifiers);
??//{b:true,c:true}
??if(props.modelModifiers.b){
??????//處理value值
??}
??if(props.modelModifiers.c){
??????//處理value值
??}
??emit("update:test1",?value);
};
</script>進階
問題背景
基礎(chǔ)篇中已經(jīng)講解了如何封裝一個自定義v-model的組件,可是在實際開發(fā)中,子組件中使用@input和:value來綁定我們的值,會比較麻煩,有沒有更簡單的辦法呢?
我們通常想要對需要雙向綁定的子組件,直接進行v-model綁定:
<!--?子組件?--> <input?type="text"?v-model="xxx"?/>
問題來了,在子組件中接受到父組件的傳值時,xxx我們應該綁定誰?直接綁定props.modelValue么?
<!--?子組件?--> <input?type="text"?v-model="props.modelValue"/>
我們會得到一個錯誤:
??reactivity.esm-bundler.js:512?Set?operation?on?key?"modelValue"?failed:?target?is?readonly.
因為props是一個readonly的值(isReadonly(props) === true),所以我們不能直接這么使用
所以,我們是需要一個中間值來綁定v-model
方式一:通過watch中轉(zhuǎn)
借助內(nèi)部變量綁定v-model,使用watch監(jiān)聽它,并同步數(shù)據(jù)props.xxx
<!--?子組件?-->
<template>
??<input?type="text"?v-model="proxy"?/>
</template>
<script?setup>
import?{?ref,?watch?}?from?"vue";
const?emit?=?defineEmits();
const?props?=?defineProps({
??modelValue:?String,
});
const?proxy?=?ref(props.modelValue);
watch(
??()?=>?proxy.value,
??(v)?=>?emit("update:modelValue",v)
);
</script>因為有時候我們雙向綁定的可能是一個對象或者數(shù)組,因此我們可以使用watch里的deep選項來深度監(jiān)聽并同步proxy;
watch(
??()?=>?proxy.value,
??(v)?=>?emit("update:modelValue",v),
??{deep:true}
);當然,props.modelValue可能存在默認值傳入,所以我們也可以加上immediate選項,使得組件在創(chuàng)建時,就直接給proxy賦上默認值;
方式二:computed的get和set
我們也可以借助computed提供的get和set來進行數(shù)據(jù)同步
const?proxy?=?computed({
??get()?{
????return?props.modelValue;
??},
??set(v)?{
????emit("update:modelValue",?v);
??},
});終極:封裝v-model的hooks
我們先來提取watch這種方式,將其封裝為一個hooks
<!--?子組件?-->
<template>
??<input?type="text"?v-model="proxy"?/>
</template>
<script?setup>
import?{?ref,?watch,?computed?}?from?"vue";
const?emit?=?defineEmits();
const?props?=?defineProps({
??modelValue:?String,
});
const?proxy?=?ref(props.modelValue);
watch(
??()?=>?proxy.value,
??(v)?=>?emit("update:modelValue",?v)
);
</script>在子組件中,我們用v-model在input上綁定了一個內(nèi)部值proxy,并以props.modelValue的值初始化proxy變量(ref(props.modelValue));
在watch中,我們監(jiān)聽input上的綁定值proxy,在input進行輸入其值變化時,向外分發(fā)emit('update:modelValue',v)事件,將改變的值動態(tài)傳到外部組件上
提取公用邏輯
//?useVmodel1.js
import?{?ref,?watch?}?from?"vue";
export?function?useVmodel(props,?emit)?{
??const?proxy?=?ref(props.modelValue);
??watch(
????()?=>?proxy.value,
????(v)?=>?emit("update:modelValue",?v)
??);
??return?proxy;
}一個最簡單的hooks便被封裝好了;
<template>
??<input?type="text"?v-model="proxy"?/>
</template>
<script?setup>
import?{?ref,?watch,?computed?}?from?"vue";
import?{?useVmodel?}?from?"./hooks/useVmodel1";
const?emit?=?defineEmits();
const?props?=?defineProps({
??modelValue:?String,
});
const?proxy?=?useVmodel(props,?emit);
</script>繼續(xù)抽離封裝
考慮到以下幾個點,繼續(xù)進行抽離封裝:
emit可以不傳,更簡潔的調(diào)用方式- 多個
v-model:test1這種情況的事件,emit("update:xxxx")中的xxxx事件名需要提取
我們可以通過vue3提供的getCurrentInstance方法,獲取當前的組件實例,而modelValue可覆蓋,則抽取成變量:
//useVmodel2.js
import?{?ref,?watch,?getCurrentInstance?}?from?"vue";
export?function?useVmodel(props,?key?=?"modelValue",?emit)?{
??const?vm?=?getCurrentInstance();
??const?_emit?=?emit?||?vm?.emit;
??const?event?=?`update:${key}`;
??const?proxy?=?ref(props[key]);
??watch(
????()?=>?proxy.value,
????(v)?=>?_emit(event,?v)
??);
??return?proxy;
}好了,現(xiàn)在我們可以更簡單的調(diào)用我們的hooks了:
<!--?子組件?childModel?-->
<template>
??<input?type="text"?v-model="modelValue"?/>
??<input?type="text"?v-model="test"?/>
</template>
<script?setup>
import?{?useVmodel?}?from?"./hooks/useVmodel2";
const?emit?=?defineEmits();
const?props?=?defineProps({
??modelValue:?String,
??test:?String,
});
const?modelValue?=?useVmodel(props);
const?test?=?useVmodel(props,?"test");
</script>
<!--?父組件?-->
<template>
??<Model?v-model="modelValue"?v-model:test="test"?/>
</template>?
<script?setup>
import?{?ref,?watch?}?from?"vue";
import?Model?from?"./childModel.vue";
const?modelValue?=?ref("");
const?test?=?ref("");
</script>到此這篇關(guān)于vue3的setup語法如何自定義v-model為公用hooks的文章就介紹到這了,更多相關(guān)vue自定義v-model內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue實現(xiàn)預覽docx/xlsx/pdf等類型文件功能
這篇文章主要介紹了如何在Vue中實現(xiàn)docx/xlsx/pdf等類型文件預覽功能,在實現(xiàn)過程中,需要注意文件的格式和轉(zhuǎn)換方式,以及插件和組件的使用方法和注意事項,需要的朋友可以參考下2023-05-05

