目录
前言:
本篇文章主要目的是为了记录,说明 vue 自定义双向绑定的实现及原理。
在自定义组件中,vue 提供了 model 选项,可以在vue中实现自定义 v-model。
在 uniapp 小程序中实现自定义 v-model 受小程序限制,详见下面说明。
v-model 的实现借助官方提供的 model 选项,其实可以理解为一个语法糖
用 props 来接收父组件的数据,子组件通过model选项派发一个更新父组件数据的方法
v-model 可以理解为下面两步:
:value="value" @input="value=$event.target.value"
来看vue 官方对 model 选项的描述:
允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 vlaue prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突。
所以这个事件不总是 input 事件,所以我们可以子组件在内部通过 model 选项派发一个 event 事件,这样在子组件数据改变的同时会更新父组件数据,达到双向绑定的效果。
注:这个 event 事件名可以是任何值
<template>
<u-datetime-picker
ref="datetimePicker"
confirmColor="#27B57D"
:mode="mode"
:title="title"
v-bind="$attrs"
v-on="$listeners"
v-model="time"
@confirm="timeChange"
:formatter="formatter"
></u-datetime-picker>
</template>
<script>
export default {
name: 'TimePicker',
model:{
// 此处的value为父组件v-model绑定数据
// mode选项将prop重写为value,则父组件中v-model绑定值会被当前组件value属性接收
prop: 'value',
// 派发事件名,更新父组件数组
event: 'updateValue'
},
props: {
// 用来接收父组件v-model传递的数据
value:{
type: [String,Number],
default:''
},
},
data() {
return {
time: this.value
}
},
methods:{
timeChange(e) {
console.log(uni.$u);
if(this.format) {
// 更新 model
this.$emit('updateValue', uni.$u.timeFormat(e.value,this.format));
} else {
// 更新 model
this.$emit('updateValue', e.value);
}
},
}
}
</script>
使用
<u-button type="primary" text="确定" @click="show = true"></u-button>
<g-time-picker
:show="show"
v-model="time"
@cancel="cancelFn"
@confirm="confirmFn"
@change="changeFn"
:minDate="1587524800000"
:maxDate="1786778555000"
:format="'yyyy年mm月dd日 hh时MM分ss秒'">
</g-time-picker>
// time 的值会传入名为 value 的 prop ***
在 uniapp 小程序项目中,自定义 v-model 的实现需要使用特定的方法,因为 uniapp 官方文档说 uniapp 小程序不支持 model 选项,详见:uniapp 中 vue 选项支持。
虽然 uniapp 官方文档说明在 uniapp 小程序中不支持 model 选项,但依然可以实现自定义 v-model。
就像 vue 官方对 model 选项的描述:
默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event。
所以在 uniapp 小程序中自定义 v-model ,model 对象中属性 event 事件名只能是 input ,同时在 子组件内部需要派发 input 事件,如果 event 事件名不是 input ,则无法生效。
注:这个 event 事件名只能是 input
model:{
prop: 'value', // 此处的value为父组件v-model绑定数据
event: 'input' // 派发事件,事件名只能是input,否则无法生效
},
methods:{
timeChange(e) {
if(this.format) {
// 更新 model
this.$emit('input', uni.$u.timeFormat(e.value,this.format));
} else {
// 更新 model
this.$emit('input', e.value);
}
},
}
上述方法亲测有效。
使用方式同上 g-time-picker 组件。
什么是 sync 修饰符呢,不清楚的可以查看这篇文章:
通俗来讲,sync 修饰符可以理解为一个语法糖,主要作用是为了更方便修改父组件的 props 数据,不必必须通过子组件向父组件通过 $emit 派发事件才能修改,即 sync 修饰符简化了在父组件中定义事件并修改父组件数据的操作。
在自定义组件中个人觉得 sync 修饰符还是很有用的,举个栗子:
通常模态框组件,例如 dialog,popup,picker,时间组件等,控制组件显示隐藏的属性一般是通过父组件传入的,所以如果要关闭弹窗则必须要向父组件中派发事件,通过事件来修改,如:
// 子组件中
props: {
show: {
type: Boolean,
default: false
},
},
methods: {
// 关闭弹窗
close() {
this.$emit('close')
},
}
// 父组件中
<template>
<pic-checkbox
:value="form.isLong"
:show="dialogShow"
@close="close">
</pic-checkbox>
</template>
<script>
export default {
name: 'TimePicker',
data() {
return {
dialogShow: false,
}
},
methods:{
close() {
this.dialogShow = false;
},
}
}
</script>
这样是不是有点麻烦,特别是如果你只是想关闭模态框而不进行其他操作的时候就会显得很麻烦,而sync 修饰符就是为了简化这一操作,使用如下:
// 子组件中
methods: {
close() {
this.$emit('close')
// 向外派发事件,用来更新父组件中的值
this.$emit('update:show',false)
},
}
父组件中使用:
// 父组件中使用sync修饰符就可以不用专门写事件方法了
<template>
<pic-checkbox
:value="form.isLong"
:show.sync="dialogShow">
</pic-checkbox>
</template>
在子组件中执行了 this.$emit('update:show',false) ,父组件中的数据将同步被更新。
当在自定义组件上使用 v-model 时,编译器会将 v-model 展开为如下的形式:
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
在vue 2.x中 v-model 使用的默认的 prop,event 分别为 value 和 input,
而在vue3.x中,默认的 props,event 被替换为 modelValue 和 update:modelValue,
那么在自定义 v-model 时,自定义组件内部需要关注两件事情:
1,将自定义组件的内部数据绑定为自定义组件的 modelValue prop
2,在自定义组件的内部数据发生变更时调用 update:modelValue 事件修改父组件中的数据
从而实现父子组件间的数据同步,案例:
export default defineComponent({
props: {
modelValue: {
type: Array,
required: true,
},
},
setup(props, { emit }) {
// 自定义组件 customerComp
const tableData = computed({
// 自定义组件内部获取父组件传递的数据
get: () => props.modelValue,
// 数据发生变化时同步修改父组件中的数据
set: (val) => {
emit('update:modelValue', val);
},
});
}
})
// 在父组件中使用
<CustomerComp v-model="data" />
默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。我们可以通过给 v-model 指定一个参数来更改默认的 prop 名:
<MyComponent v-model:tableData="data" />
那么在子组件中应该声明一个名为 tableData 的 prop,并使用 update:tableData 的事件的更新父组件中的数据:
// 自定义组件 customerComp
const tableData = computed({
// 自定义组件内部获取父组件传递的数据
get: () => props.tableData,
// 数据发生变化时同步修改父组件中的数据
set: (val) => {
emit('update:tableData', val);
},
});
更多 v-model 配置请参考: vue-配合v-model使用