一、Vue3.4 v-model 基本用法
1. 在原生元素上的应用
在 Vue3.4 中,v-model 在原生元素上的应用与 Vue2 类似,主要用于表单元素如 input、textarea 以及 select 等。例如,等价于<input :value="searchText" @input="searchText = $event.target.value"/>。
2. 在组件上的应用
在 Vue3.4 中,通过defineModel()宏可以实现双向绑定,使得父组件和子组件之间的数据传递更加简洁高效。
1. 通过defineModel()宏实现双向绑定
父组件可以使用v-model绑定一个值到子组件,子组件中可以通过defineModel()宏来接收这个值。例如:
<!-- 父组件 -->
<template>
<MyComponent v-model="bookTitle" />
</template>
<script setup>
import MyComponent from './MyComponent.vue';
const bookTitle = ref('v-model argument example');
</script>
在子组件中,可以通过将字符串作为第一个参数传递给defineModel()来支持相应的参数。如果需要额外的 prop 选项,应在 model 名称之后传递:
<!-- 子组件 -->
<template>
<input type="text" v-model="title" />
</template>
<script setup>
const title = defineModel('title', { required: true });
</script>
2. 父组件如何使用v-model绑定一个值
父组件在使用v-model时,相当于给子组件传入了名为modelValue的 prop,并监听了子组件的自定义事件update:modelValue。例如:
<!-- 父组件 -->
<template>
<ChildComponent v-model="someData" />
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
const someData = ref('initial value');
</script>
3. 子组件中对应的实现方式
子组件需要声明接收modelValue作为 prop,并在需要更新父组件数据时触发update:modelValue事件。以下是两种子组件实现v-model双向绑定的方式:
方式一:使用 defineModel() 宏
<!-- 子组件 -->
<template>
<input v-model="model" />
</template>
<script setup>
const model = defineModel();
</script>
defineModel()返回的值是一个 ref,它的.value和父组件的v-model的值同步。当它被子组件变更时,会触发父组件绑定的值一起更新。
方式二:使用 value 和 input 事件
<!-- 子组件 -->
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>
<script setup>
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
</script>
子组件接收modelValue作为 prop,并在输入框的input事件中触发event.target.value),将新的值传递给父组件。
二、底层机制
- 解释defineModel是一个便利宏,编译器将其展开的内容,包括一个名为modelValue的 prop 和一个名为update:modelValue的事件,并阐述其工作原理。
defineModel是一个便利宏,在 Vue3.4 中用于实现组件的双向绑定。编译器会将其展开为以下内容:一个名为modelValue的 prop,本地 ref 的值与其同步;一个名为update:modelValue的事件,当本地 ref 的值发生变更时触发。
从底层机制来看,在 3.4 版本之前,实现相同的子组件通常需要手动声明modelValue的 prop 和update:modelValue的事件,如:
<!-- Child.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)"/>
</template>
然后,父组件中的v-model="modelValue"将被编译为:
<!-- Parent.vue -->
<Child :modelValue="foo" @update:modelValue="$event => (foo = $event)"/>
相比之下,使用defineModel显得更加简洁高效。例如:
<!-- Child.vue -->
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model"/>
</template>
defineModel返回的值是一个 ref,它的.value和父组件的v-model的值同步。当它被子组件变更时,会触发父组件绑定的值一起更新。
这是因为defineModel在子组件内定义了一个叫model的 ref 变量和modelValue的 props,并且 watch 了 props 中的modelValue。当 props 中的modelValue的值改变后会同步更新model变量的值。并且当在子组件内改变model变量的值后会抛出update:modelValue事件,父组件收到这个事件后就会更新父组件中对应的变量值。
因为defineModel声明了一个 prop,所以可以通过给defineModel传递选项,来声明底层 prop 的选项。比如:
- 使v-model必填:const model = defineModel({ required: true });
- 提供一个默认值:const model = defineModel({ default: 0 })。
三、v-model 的参数
- 介绍如何通过给 v-model 指定一个参数来更改默认的 prop 和事件名,以及在子组件中如何支持相应的参数。
在 Vue3.4 中,默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。然而,我们可以通过给 v-model 指定一个参数来更改这些默认的名字。
例如:,在这个例子中,子组件应声明一个 title prop,并通过触发 update:title 事件更新父组件值。
子组件的实现方式如下:
<!-- MyComponent.vue -->
<script setup>
defineProps(['title']);
defineEmits(['update:title']);
</script>
<template>
<input type="text" :value="title" @input="$emit('update:title', $event.target.value)" />
</template>
通过这种方式,我们可以灵活地自定义 v-model 绑定的属性名和事件名,增加了组件的通用性和可扩展性。
四、多个 v-model 绑定
- 利用指定参数与事件名的技巧,在单个组件实例上创建多个 v-model 双向绑定,以及在子组件中的实现方式。
在 Vue3.4 中,我们可以利用给 v-model 指定参数与事件名的技巧,在单个组件实例上创建多个 v-model 双向绑定。例如,,这样的写法可以让组件上的每一个 v-model 同步不同的 prop,无需额外的选项。
在子组件中,我们可以通过将字符串作为第一个参数传递给defineModel()来支持相应的参数。如果需要额外的 prop 选项,应在 model 名称之后传递。以下是具体的实现方式:
<!-- 子组件 -->
<script setup>
const firstName = defineModel('firstName');
const lastName = defineModel('lastName');
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
在上述代码中,子组件接收了两个不同的参数,分别对应父组件传递过来的firstName和lastName。当子组件中的输入框的值发生变化时,会通过defineModel()自动触发相应的事件,更新父组件中的对应数据。
这种多个 v-model 绑定的方式在实际开发中非常有用,可以让我们在一个组件中同时处理多个不同的数据双向绑定,提高了代码的灵活性和可维护性。例如,在一个用户信息编辑组件中,我们可以同时绑定用户的姓名、年龄、性别等多个属性,方便地进行数据的同步和更新。
五、处理 v-model 修饰符
- 讲解内置修饰符如.trim、.number 和.lazy,以及如何创建自定义修饰符,通过解构 defineModel () 的返回值访问修饰符,并使用 get 和 set 选项调节值的读取和写入方式。
在 Vue3.4 中,v-model 有一些内置的修饰符,例如 .trim、.number 和 .lazy。这些内置修饰符可以方便地对输入值进行特定的处理。
- .trim:自动去除输入值的首尾空格。例如,,当用户在输入框中输入内容时,输入值的首尾空格会被自动去除。
- .number:将输入值转换为数字类型。例如,,如果用户输入的是字符串形式的数字,会自动转换为数字类型。
- .lazy:将输入的同步方式从实时更新改为在失去焦点或按下回车键时更新。例如,,只有在输入框失去焦点或用户按下回车键时,绑定的值才会更新。
除了内置修饰符,我们还可以创建自定义修饰符。例如,创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写。
在子组件中,可以通过解构 defineModel () 的返回值来访问添加到组件 v-model 的修饰符。子组件的代码如下:
<script setup>
const [modelValue, modelModifiers] = defineModel({
// get 我们这里不需要
set(value) {
if (modelModifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
return value;
}
});
</script>
<template>
<input type="text" v-model="modelValue" />
</template>
在上述代码中,我们通过解构 defineModel () 的返回值得到 modelValue 和 modelModifiers。在 set 选项中,我们检查 modelModifiers.capitalize 是否为 true,如果是,则将输入值的第一个字母转为大写后返回。
通过这种方式,我们可以根据特定的需求创建自定义修饰符,灵活地处理输入值。同时,使用 get 和 set 选项可以更加精细地控制值的读取和写入方式,满足不同场景下的需求。