vue3封裝自定義v-model的hooks示例詳解
??前言
- 基礎(chǔ)篇:簡單介紹
vue3
的setup
語法如何自定義v-model
; - 進階篇:如何提取
v-model
語法作為一個公用hooks
;
??基礎(chǔ)
基礎(chǔ)篇可繞過,只是對于官網(wǎng)給出的教程,進行了總結(jié)概括并給出demo
基本的v-model
子組件中滿足兩個點,即可完成自定義雙向綁定:
props
中定義一個值xxx
emit
中定義一個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
和test2
emits
中定義兩個事件,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
中定義兩個值
modelValue
modelModifiers
,接受修飾符key
值
對于自定義v-model:xxx
來說,props
中:
xxx
xxxModeifiers
,接受修飾符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>
最后
封裝computed
這種方式本文暫不贅述,小伙伴們可進行自行封裝抽離,以上就是vue3自定義封裝v-model的hooks示例詳解的詳細內(nèi)容,更多關(guān)于vue3封裝v-model hooks的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue.nextTick()與setTimeout的區(qū)別及說明
這篇文章主要介紹了vue.nextTick()與setTimeout的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03Vue+Echarts報錯Cannot?set?properties?of?undefined?(settin
這篇文章主要介紹了Vue+Echarts報錯Cannot?set?properties?of?undefined?(setting?‘plate‘)的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08vue-simple-uploader上傳成功之后的response獲取代碼
這篇文章主要介紹了vue-simple-uploader上傳成功之后的response獲取代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧~2020-09-09vue-router路由跳轉(zhuǎn)問題 replace
這篇文章主要介紹了vue-router路由跳轉(zhuǎn)問題 replace,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09vue 權(quán)限認證token的實現(xiàn)方法
這篇文章主要介紹了vue 權(quán)限認證token的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07Vue3中實現(xiàn)發(fā)送網(wǎng)絡(luò)請求功能(最新推薦)
Axios是一個基于Promise的HTTP客戶端,可以在瀏覽器和Node.js中用于發(fā)送HTTP請求,本文主要介紹在Vue3中實現(xiàn)發(fā)送網(wǎng)絡(luò)請求功能,感興趣的朋友一起看看吧2023-12-12