
vue3的v-model
·
组件 v-model
v-model 可以在组件上使用以实现双向绑定
1. v-model双向绑定
1.1 HTML表单元素v-model双向绑定
-
原生的表单元素 使用
v-model双向绑定
<input v-model="searchText" />
-
模板编译器会对上面的 v-model 进行如下更冗长的
等价展开
。- 将组件实例上的searchText绑定给input的value属性
- 监听input元素的输入事件,触发对应的监听函数执行,监听函数从输入事件中获取到输入的值,将输入的值更新给组件实例上的searchText,然后由于修改了响应式数据,引发重新渲染。
<input :value="searchText" @input="searchText = $event.target.value" />
示例
MyInput.vue
<template>
{{ searchText }}
<input type="text" v-model="searchText"> <br/>
<!-- 将被展开为如下形式(以下,同上面等价) -->
<!-- <input type="text" :modelValue="searchText" @input="searchText = $event.target.value"> -->
</template>
<script>
export default {
name: 'MyInput',
data() {
return {
searchText:''
}
}
}
</script>
<style lang="scss"></style>
1.2 自定义组件实现v-model
- 可以在一个自定义组件上使用v-model
<custom-input v-model="searchText"/>
- 当在一个自定义组件上使用v-model时,v-model 会被展开为如下的形式
- 将父组件实例上的searchText绑定给自定义组件的
modelValue属性
- 在父组件中,监听自定义组件的
update:modelValue事件
,当自定义组件使用$emit触发update:modelValue事件
时(并携带了事件参数),对应的监听函数将执行,并且将事件参数的值更新给父组件实例上的searchText,由于修改了响应式数据,从而引发重新渲染(这个可以验证出来)
<CustomInput :modelValue="searchText" @update:modelValue="newValue => searchText = newValue" />
- 将父组件实例上的searchText绑定给自定义组件的
示例
MyInput.vue
- 父组件MyInput将自身的searchTex属性,通过
v-model
绑定给了自定义组件CustomInput组件。这实际上等价于做了2件事情:- 将父组件的searchText属性以props的形式传递给了CustomInput组件的modelValue的prop,
- 并且给CustomInput组件绑定了对update:modelValue的事件监听,将事件触发时传递过来的事件参数,更新给了父组件MyInput的searchText属性,由于修改了响应式数据,从而引发重新渲染(这个可以验证出来)
<template>
{{ searchText }}
<custom-input v-model="searchText"/>
<!-- 将被展开为如下形式(以下,同上面等价) -->
<!--<custom-input :modelValue="searchText" @update:modelValue="newVal => searchText = newVal"/>-->
</template>
<script>
import CustomInput from './CustomInput.vue';
export default {
name: 'MyInput',
components:{
CustomInput
},
data() {
return {
searchText:''
}
}
}
</script>
<style lang="scss"></style>
CustomInput.vue
子组件中仍然需要监听input事件,通过$emit触发update:modelValue事件,并且,将输入的值作为事件参数传递给事件监听函数
<template>
<input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
{{ modelValue }}
</template>
<script>
export default {
name: 'CustomInput',
data() {
return {
}
},
props: ['modelValue']
}
</script>
<style lang="scss"></style>
示例2(验证重新渲染)
- 输入3次2,然后出现了如图的效果
- 每次输入2的时候,子组件通过监听input事件,使用$emit触发update:modelValue事件,并且在输入的值后拼接上1作为事件参数,监听函数则把事件参数更新给了searchText,由于响应式数据发生了变化,所以重新渲染,所以输入框中,又是添加上了1,这证明了,修改了响应式数据后,子组件由于使用了prop,而得到了更新
MyInput.vue
<template>
{{ searchText }}
<custom-input v-model="searchText"/>
</template>
<script>
import CustomInput from './CustomInput.vue';
export default {
name: 'MyInput',
components:{
CustomInput
},
data() {
return {
searchText:''
}
}
}
</script>
<style lang="scss"></style>
CustomInput.vue
<template>
<input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value + 1)">
{{ modelValue }}
</template>
<script>
export default {
name: 'CustomInput',
data() {
return {
}
},
props: ['modelValue'],
updated() {
console.log('updated...');
}
}
</script>
<style lang="scss"></style>
1.3 使用computed属性实现v-model双向绑定
- 另一种在组件内实现 v-model 的方式是使用一个可写的,同时具有 getter 和 setter 的 computed 属性。
- get 方法需返回 modelValue prop,而 set 方法需触发相应的事件:
示例1
MyInput.vue
<template>
{{ searchText }}
<custom-input v-model="searchText"/>
<!-- 将被展开为如下形式(以下,同上面等价) -->
<!--<custom-input :modelValue="searchText" @update:modelValue="newVal => searchText = newVal"/>-->
</template>
<script>
import CustomInput from './CustomInput.vue';
export default {
name: 'MyInput',
components:{
CustomInput
},
data() {
return {
searchText:''
}
}
}
</script>
<style lang="scss"></style>
CustomInput.vue
- 子组件CustomInput通过props接收modelValue属性,并将计算属性绑定给了input(计算属性的值来源于getter,即传入的modelValue这个prop)
- 监听input元素的输入事件,并给计算属性赋值,触发计算属性的setter,在这个setter方法中,触发了update:modelValue事件,并将输入的值传递了过去,并更新给了searchText,然后引发重新渲染。
<template>
<input type="text" :value="value" @input="value = $event.target.value">
{{ modelValue }}
</template>
<script>
export default {
name: 'CustomInput',
data() {
return {
}
},
props: ['modelValue'],
computed: {
value: {
get() {
return this.modelValue
},
set(val) {
this.$emit('update:modelValue', val)
}
}
},
}
</script>
<style lang="scss"></style>
示例2
Test.vue
<template>
<!-- 3. 使用computed属性实现v-model双向绑定 -->
父组件中: {{ value1 }}<br/>
子组件中: <custom-input2 v-model="value1"/>
</template>
<script lang="ts" setup>
import { ref,reactive } from 'vue'
import CustomInput2 from '@/views/test/CustomInput2.vue'
const value1 = ref("zzhua")
</script>
<style lang="scss">
</style>
CustomInput2.vue
<template>
<!-- <input v-model="value" /> -->
<!-- 以上等价于下方 -->
<input :value="value" @input="value = $event.target.value"/>
{{ modelValue }} - {{ value }}
<!-- 过程分析:
1. 父组件通过v-model指令, 将值通过props的方式传递给了子组件定义的modelValue
2. 子组件中使用计算属性value, 读此属性返回modelValue, 修改此属性将触发 update:modelValue事件
3. 当子组件初始化时, 模板被渲染, 拿到父组件传过来的modelValue,
模板中用到计算属性value的地方,计算属性value的get将被触发,于是返回modeValue的值
4. 当修改子组件input框中的内容时, 将会把修改后的内容赋值给计算属性value,
计算属性value的set将会触发,将会把修改后的内容交给父组件的update:modelValue事件处理函数,
从而子组件又开始更新渲染模板,于是跟第三步是一样的
-->
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
3. v-model的参数
-
默认情况下
,v-model 在组件上都是使用modelValue
作为 prop,并以 update:modelValue 作为对应的事件。 -
我们可以通过给 v-model 指定一个参数来更改这些名字,如下:
<MyComponent v-model:title="bookTitle" />
-
在这个例子中,子组件应声明一个 title 的prop,并通过触发 update:title 事件更新父组件值:
<!-- MyComponent.vue --> <script> export default { props: ['title'], emits: ['update:title'] } </script> <template> <input type="text" :value="title" @input="$emit('update:title', $event.target.value)" /> </template>
-
可以在单个组件实例上,创建多个v-model双向绑定,组件上的每一个 v-model 都会同步不同的 prop,而无需额外的选项:
<UserName v-model:first-name="first" v-model:last-name="last" />
<!-- UserName组件 --> <script> export default { props: { firstName: String, lastName: String }, emits: ['update:firstName', 'update:lastName'] } </script> <template> <input type="text" :value="firstName" @input="$emit('update:firstName', $event.target.value)" /> <input type="text" :value="lastName" @input="$emit('update:lastName', $event.target.value)" /> </template>
示例
Test.vue
<template>
<!-- 4. v-model:attr 自定义v-model对应的prop参数名 -->
<!-- 默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,
并以 update:modelValue 作为对应的事件。
我们可以通过给 v-model 指定一个参数来更改这些名字
-->
<CustomInput3 v-model:title="bookTitle" />
<!-- 这等价于如下 -->
<!--<CustomInput3 :title="bookTitle" @update:title="newVal => bookTitle = newVal"/>-->
</template>
<script lang="ts" setup>
import { ref,reactive } from 'vue'
import CustomInput3 from '@/views/test/CustomInput3.vue';
const bookTitle = ref("bookTitle")
</script>
<style lang="scss"></style>
CustomInput3
<template>
<input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
{{ title }}
</template>
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
5. v-model支持修饰符
在学习输入绑定时,我们知道了 v-model 有一些内置的修饰符,例如 .trim,.number 和 .lazy。在某些场景下,你可能想要一个自定义组件的 v-model 支持自定义的修饰符。
自定义一个v-model修饰符
-
创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写
<MyComponent v-model.capitalize="myText" />
-
组件的 v-model 上所添加的修饰符,可以通过 modelModifiers prop 在组件内访问到。在下面的组件中,我们声明了 modelModifiers 这个 prop,它的默认值是一个空对象:
- 注意这里组件的 modelModifiers prop 包含了 capitalize 且其值为 true,因为它在模板中的 v-model 绑定 v-model.capitalize=“myText” 上被使用了。
- 有了这个 prop,我们就可以检查 modelModifiers 对象的键,并编写一个处理函数来改变抛出的值。在下面的代码里,我们就是在每次 元素触发 input 事件时将值的首字母大写:
<script> export default { props: { modelValue: String, modelModifiers: { default: () => ({}) } }, emits: ['update:modelValue'], created() { console.log(this.modelModifiers) // { capitalize: true } } } </script> <template> <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template>
v-model参数 + 修饰符
对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是 arg + “Modifiers”。
<MyComponent v-model:title.capitalize="myText">
相应的声明应该是:
export default {
props: ['title', 'titleModifiers'],
emits: ['update:title'],
created() {
console.log(this.titleModifiers) // { capitalize: true }
}
}
示例
Test.vue
<template>
<!-- 5. v-model的修饰符 -->
<MyComponent v-model.capitalize="myText" />
</template>
<script lang="ts" setup>
import { ref,reactive } from 'vue'
import MyComponent from '@/views/test/MyComponent.vue';
const myText = ref("myText")
</script>
<style lang="scss">
</style>
MyComponent.vue
<template>
<input
type="text"
:value="modelValue"
@input="handleInput"
/>
{{ modelValue }}
</template>
<script lang="ts" setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) } /* 这个prop可以拿到修饰符 */
})
console.log(props.modelModifiers) // { capitalize: true }
const emit = defineEmits(['update:modelValue'])
// 处理input事件
function handleInput(e) {
let value = e.target.value
// 根据是否存在修饰符, 作进一步处理
if(props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<style lang="scss">
</style>
更多推荐
所有评论(0)