基础篇可绕过,只是对于官网给出的教程,进行了总结概括并给出demo
子组件中满足两个点,即可完成自定义双向绑定:
下面我们来写一个最基本的v-model组件:
1.props中定义一个modelValue值,并绑定到input的value属性上;
2.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组件;
当我们需要多个双向绑定时,如下:
<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>
复制代码
子组件中,同样按着两个点来定义:
1.props中定义两个值,test1和test2
2.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>
复制代码
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两个修饰符;
对于修饰符,我们需要满足以下条件:
- modelValue
- modelModifiers,接受修饰符key值
- 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>
复制代码
基础篇中已经讲解了如何封装一个自定义v-model的组件,可是在实际开发中,子组件中使用@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
借助内部变量绑定v-model,使用watch监听它,并同步数据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>
复制代码
因为有时候我们双向绑定的可能是一个对象或者数组,因此我们可以使用watch里的deep选项来深度监听并同步proxy;
watch(
() => proxy.value,
(v) => emit("update:modelValue",v),
{deep:true}
);
复制代码
当然,props.modelValue可能存在默认值传入,所以我们也可以加上immediate选项,使得组件在创建时,就直接给proxy赋上默认值;
我们也可以借助computed提供的get和set来进行数据同步
const proxy = computed({
get() {
return props.modelValue;
},
set(v) {
emit("update:modelValue", v);
},
});
复制代码
我们先来提取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上绑定了一个内部值proxy,并以props.modelValue的值初始化proxy变量(ref(props.modelValue));
在watch中,我们监听input上的绑定值proxy,在input进行输入其值变化时,向外分发emit('update:modelValue',v)事件,将改变的值动态传到外部组件上
// 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>
复制代码
考虑到以下几个点,继续进行抽离封装:
我们可以通过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;
}
复制代码
好了,现在我们可以更简单的调用我们的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>
源码附件已经打包好上传到百度云了,大家自行下载即可~
链接: https://pan.baidu.com/s/14G-bpVthImHD4eosZUNSFA?pwd=yu27
提取码: yu27
百度云链接不稳定,随时可能会失效,大家抓紧保存哈。
如果百度云链接失效了的话,请留言告诉我,我看到后会及时更新~
码云地址:
http://github.crmeb.net/u/defu
Github 地址:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。