vue3封裝自定義v-model的hooks示例詳解
??前言
- 基礎(chǔ)篇:簡(jiǎn)單介紹
vue3的setup語(yǔ)法如何自定義v-model; - 進(jìn)階篇:如何提取
v-model語(yǔ)法作為一個(gè)公用hooks;
??基礎(chǔ)
基礎(chǔ)篇可繞過(guò),只是對(duì)于官網(wǎng)給出的教程,進(jìn)行了總結(jié)概括并給出demo
基本的v-model
子組件中滿足兩個(gè)點(diǎn),即可完成自定義雙向綁定:
props中定義一個(gè)值xxxemit中定義一個(gè)update:xxx事件
下面我們來(lái)寫(xiě)一個(gè)最基本的v-model組件:
props中定義一個(gè)modelValue值,并綁定到input的value屬性上;emit中定義一個(gè)update:modelValue事件
需要注意的是,當(dāng)modelValue作為props傳入,update:modelValue事件將被自動(dòng)注冊(cè)到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>
這便是一個(gè)最基本的自定義v-model組件;
多個(gè)v-model綁定
當(dāng)我們需要多個(gè)雙向綁定時(shí),如下:
<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>
子組件中,同樣按著兩個(gè)點(diǎn)來(lái)定義:
props中定義兩個(gè)值,test1和test2emits中定義兩個(gè)事件,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>
在一些場(chǎng)景下,我們需要自己定義修飾符,來(lái)滿足我們的需求,舉個(gè)栗子:
<modelComp v-model.a="test" v-model:test1.b.c="test1" ></modelComp>
默認(rèn)v-model中我們綁定了a修飾符,v-model:test1中則綁定b和c兩個(gè)修飾符;
對(duì)于修飾符,我們需要滿足以下條件:
對(duì)于默認(rèn)v-model來(lái)說(shuō),需要props中定義兩個(gè)值
modelValuemodelModifiers,接受修飾符key值
對(duì)于自定義v-model:xxx來(lái)說(shuō),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>
??進(jìn)階
問(wèn)題背景
基礎(chǔ)篇中已經(jīng)講解了如何封裝一個(gè)自定義v-model的組件,可是在實(shí)際開(kāi)發(fā)中,子組件中使用@input和:value來(lái)綁定我們的值,會(huì)比較麻煩,有沒(méi)有更簡(jiǎn)單的辦法呢?
我們通常想要對(duì)需要雙向綁定的子組件,直接進(jìn)行v-model綁定:
<!-- 子組件 --> <input type="text" v-model="xxx" />
問(wèn)題來(lái)了,在子組件中接受到父組件的傳值時(shí),xxx我們應(yīng)該綁定誰(shuí)?直接綁定props.modelValue么?
<!-- 子組件 --> <input type="text" v-model="props.modelValue"/>
我們會(huì)得到一個(gè)錯(cuò)誤:
??reactivity.esm-bundler.js:512 Set operation on key "modelValue" failed: target is readonly.
因?yàn)?code>props是一個(gè)readonly的值(isReadonly(props) === true),所以我們不能直接這么使用
所以,我們是需要一個(gè)中間值來(lái)綁定v-model
方式一:通過(guò)watch中轉(zhuǎn)
借助內(nèi)部變量綁定v-model,使用watch監(jiān)聽(tīng)它,并同步數(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>
因?yàn)橛袝r(shí)候我們雙向綁定的可能是一個(gè)對(duì)象或者數(shù)組,因此我們可以使用watch里的deep選項(xiàng)來(lái)深度監(jiān)聽(tīng)并同步proxy;
watch(
() => proxy.value,
(v) => emit("update:modelValue",v),
{deep:true}
);
當(dāng)然,props.modelValue可能存在默認(rèn)值傳入,所以我們也可以加上immediate選項(xiàng),使得組件在創(chuàng)建時(shí),就直接給proxy賦上默認(rèn)值;
方式二:computed的get和set
我們也可以借助computed提供的get和set來(lái)進(jìn)行數(shù)據(jù)同步
const proxy = computed({
get() {
return props.modelValue;
},
set(v) {
emit("update:modelValue", v);
},
});
??終極:封裝v-model的hooks
我們先來(lái)提取watch這種方式,將其封裝為一個(gè)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上綁定了一個(gè)內(nèi)部值proxy,并以props.modelValue的值初始化proxy變量(ref(props.modelValue));
在watch中,我們監(jiān)聽(tīng)input上的綁定值proxy,在input進(jìn)行輸入其值變化時(shí),向外分發(fā)emit('update:modelValue',v)事件,將改變的值動(dòng)態(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;
}
一個(gè)最簡(jiǎn)單的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ù)抽離封裝
考慮到以下幾個(gè)點(diǎn),繼續(xù)進(jìn)行抽離封裝:
emit可以不傳,更簡(jiǎn)潔的調(diào)用方式- 多個(gè)
v-model:test1這種情況的事件,emit("update:xxxx")中的xxxx事件名需要提取
我們可以通過(guò)vue3提供的getCurrentInstance方法,獲取當(dāng)前的組件實(shí)例,而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)在我們可以更簡(jiǎ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>
最后
封裝computed這種方式本文暫不贅述,小伙伴們可進(jìn)行自行封裝抽離,以上就是vue3自定義封裝v-model的hooks示例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue3封裝v-model hooks的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue.nextTick()與setTimeout的區(qū)別及說(shuō)明
這篇文章主要介紹了vue.nextTick()與setTimeout的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Vue+Echarts報(bào)錯(cuò)Cannot?set?properties?of?undefined?(settin
這篇文章主要介紹了Vue+Echarts報(bào)錯(cuò)Cannot?set?properties?of?undefined?(setting?‘plate‘)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
vue-simple-uploader上傳成功之后的response獲取代碼
這篇文章主要介紹了vue-simple-uploader上傳成功之后的response獲取代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧~2020-09-09
Vue.js實(shí)現(xiàn)可排序的表格組件功能示例
這篇文章主要介紹了Vue.js實(shí)現(xiàn)可排序的表格組件功能,涉及vue.js事件響應(yīng)、元素動(dòng)態(tài)操作、排序、運(yùn)算等相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
vue-router路由跳轉(zhuǎn)問(wèn)題 replace
這篇文章主要介紹了vue-router路由跳轉(zhuǎn)問(wèn)題 replace,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
利用v-viewer圖片預(yù)覽插件放大需要預(yù)覽的圖片
本文介紹了v-viewer插件的安裝和使用步驟,包括npm安裝、在main.js文件中全局引入,以及常用的三種使用方式,文章提供了簡(jiǎn)單的布局頁(yè)面效果,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10
vue項(xiàng)目中icon亂碼的問(wèn)題及解決
這篇文章主要介紹了vue項(xiàng)目中icon亂碼的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
vue 權(quán)限認(rèn)證token的實(shí)現(xiàn)方法
這篇文章主要介紹了vue 權(quán)限認(rèn)證token的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Vue3中實(shí)現(xiàn)發(fā)送網(wǎng)絡(luò)請(qǐng)求功能(最新推薦)
Axios是一個(gè)基于Promise的HTTP客戶端,可以在瀏覽器和Node.js中用于發(fā)送HTTP請(qǐng)求,本文主要介紹在Vue3中實(shí)現(xiàn)發(fā)送網(wǎng)絡(luò)請(qǐng)求功能,感興趣的朋友一起看看吧2023-12-12

