组件v-model
v-model
是vue里面非常重要的一个内置指令,作用是在表单输入元素或者组件上创建双向绑定,这些元素仅包含:
<input>
<select>
<textarea>
- components
v-model
其实就是value
属性和input
事件的语法糖
<input type="text" :value="iptVal" @input="$event => iptVal = $event.target.value" />
<!-- v-model -->
<input type="text" v-model="iptVal" />
我们在表单开发时通常会使用v-model
指令来完成数据绑定,组件components上同样可以使用该指令。
基本使用
v-model
可以在组件上使用以实现双向绑定。上面的示例是v-mdoel
在表单元素上的语法糖使用,而当在一个组件上使用时,它其实是modelValue
属性和update:modelValue
事件的语法糖
<IptCpn
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
<!-- v-model -->
<IptCpn v-model="searchText" />
在组件内部需要做两件事来实现功能,参见官方文档的两句话:
- 将内部原生
<input>
元素的value
attribute 绑定到modelValue
prop - 当原生的
input
事件触发时,触发一个携带了新值的update:modelValue
自定义事件
所以子组件代码为:
<!-- IptCpn.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
type="text"
:value="modelValue"
@input="$event => $emit('update:modelValue', $event.target.value)"
/>
</template>
这时组件v-model
就可以工作了,但是子组件中的元素还是必须绑定value
属性和监听input
事件,如果子组件中的表单元素也想使用语法糖写法v-model
来绑定状态,需要使用拥有getter 和 setter 的computed
来编写:
<!-- IptCpn.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const modelValueComputed = computed({
set(val) {
emit('update:modelValue', val)
},
get() {
return props.modelValue
}
})
</script>
<template>
<input
type="text"
v-model="modelValueComputed"
/>
</template>
修饰符
v-model
有一些内置的修饰符,例如 .trim
,.number
和 .lazy
。这些在组件v-model是同样可以使用的,并且支持自定义修饰符来扩展一些功能
<IptCpn v-model.trim="searchText" />
多个v-model
组件上的v-model可以使用多个,使用v-model传参(就是给v-model一个名字)
<!-- 父 -->
<IptCpn v-model="prop1" v-model:title="prop2"/>
<!-- IptCpn.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue', 'title'])
const emit = defineEmits(['update:modelValue', 'update:title'])
const modelValueComputed = computed({
set(val) {
emit('update:modelValue', val)
},
get() {
return props.modelValue
}
})
const titleComputed = computed({
set(val) {
emit('update:title', val)
},
get() {
return props.title
}
})
</script>
<template>
<input
type="text"
v-model="modelValueComputed"
/>
<input
type="text"
v-model="titleComputed"
/>
</template>
这样就可以在单个组件实例上创建多个 v-model
双向绑定,但是这种写法冗余代码太多,每增加一个v-model
绑定就会多写一个computed
,如果表单元素过多,那么代码量就会增加很多。
优化
我们来思考一下,子组件中的多个表单元素绑定的数据可以看成是一类状态,我们将这一类状态放进一个对象中再使用v-model
绑定到组件中:
<IptCpn v-model="formData" />
这里有一个需要注意的地方,我们现在再输入框中输入值,改变的是对象里面的某个字段的值,而不是这个对象,所以不会进入到setter
当中,我们可以使用Proxy
来代理整个对象,当每个值发生更改时,都做一次emit
<script setup>
import { computed } from "vue"
const props = defineProps({
modelValue: {
type: Object,
require: true,
},
})
const emit = defineEmits(["update:modelValue"])
const modelValueComputed = computed({
set(val) {
// ❌
emit("update:modelValue", val)
},
get() {
//✅
return new Proxy(props.modelValue, {
set(target, name, value) {
target[name] = value
emit("update:modelValue", target)
return true
},
})
},
})
</script>
<template>
<input type="text" placeholder="姓名" v-model="modelValueComputed.name" />
<input type="text" placeholder="年龄" v-model="modelValueComputed.age" />
</template>
在实际开发中,还可以将该部分代码抽成一个hook,使其成为一个通用的方法,以便使用。
转载自:https://juejin.cn/post/7220352362799743013