双向数据绑定在前端最早用在 KO、AngularJS 这些 MVVM 框架中,它是 Vue 从诞生就一直有的特性。使用 v-model 可以在组件上实现双向绑定,方便表单应用的开发。相比 Vue 2,Vue 3 的双向绑定功能得到了增强,具体表现在以下几个方面:
- 支持
v-model的参数 - 支持多个
v-model绑定 - 支持自定义
v-model修饰符
v-model 的参数
在 Vue 2 中,默认情况下,v-model 在组件上都是使用 value 作为 prop,并以 input 作为对应的事件。对于单选框、复选框等类型的单个输入控件,可以通过设置 model 选项的 prop 为 checked, event 为 change。无论设不设置 model 选项,这些名称都是固定不变的。
在 Vue 3 中,默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。
<!-- CustomInput.vue --> <script setup lang="ts"> defineProps<{ modelValue?: string }>() const emit = defineEmits<{ (e: 'update:modelValue', vlaue: string): void }>() </script> <template> <input :value="modelValue" @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)" /> </template>这样我们就自定义了一个支持 v-model 的 Vue 3 组件。在宿主组件中使用时与 Vue 2 中相同:
<!-- App.vue --> <script lang="ts" setup> import { ref } from 'vue' import CustomInput from './components/CustomInput.vue' const message = ref('hello') </script> <template> <CustomInput `v-model`="message" /> <p>{{ message }}</p> </template>不同的是,在 Vue 3 中,prop 和事件的名称时可变的:
<script setup lang="ts"> defineProps<{ title?: string }>() const emit = defineEmits<{ (e: 'update:title', vlaue: string): void }>() </script> <template> <input :value="modelValue" @input="emit('update:title', ($event.target as HTMLInputElement).value)" /> </template>上述示例中,我们为自定义组件设置了 title prop 和 update:title 事件。因此,使用时,我们要给 v-model 指定一个参数来更改这些名称,如下面示例中的参数 title:
<template> <CustomInput `v-model`:title="message" /> <p>{{ message }}</p> </template>默认的v-model参数为modelValue,为了简便,一般在使用是会省去该参数。
注意:双向绑定 prop 和事件名也不是任意的,规则是:事件名 = update: + prop 名,这和为组件 prop 自定义 .sync 修饰符时需要遵从的模式类似。
为 v-model 参数指定参数与事件名不仅表达了绑定的意图,更重要的意义在于,它使绑定多个 v-model成为可能。
绑定多个 v-model
在 Vue 2 中,只能绑定一个 v-model,但可以通过 .sync 修饰符“双向绑定”多个 prop。Vue 3 借鉴并简化了 .sync 修饰符模式,直接支持多个 v-model 绑定。
// ... import { computed } from 'vue' const props = withDefaults( defineProps<{ modelValue?: string percent?: number modelModifiers?: { capitalize: boolean } }>(), { modelValue: '', percent: 0 } ) const emit = defineEmits<{ (e: 'update:modelValue', value: string): void (e: 'update:percent', value: number): void }>() const changeValue = (event: Event) => { let value = (event.target as HTMLInputElement).value emit('update:modelValue', value) } const current = computed({ get() { return Math.round(props.percent * 100) }, set(value: number) { emit('update:percent', value / 100) } })上述示例中,我们自定义了两个 v-model: modelValue 和 percent。
如你所见,上述示例在组件内实现 v-model 的方式是使用一个可写的 computed 属性。get 方法需返回 percent prop,而 set 方法需触发相应的 update:percent 事件。
组件上的每一个 v-model 都会同步不同的 prop,而无需额外的选项:
<Form v-model="name" v-model:percent.number="percent" />有了多个 v-model 绑定,可以不用再为表单中的每个字段都自定义一个支持 v-model 的组件,现在可以只自定义一个表单组件,每个字段绑定一个 v-model。
自定义 v-model 修饰符
在 Vue 2 中,我们可以在 v-model 上使用一些内置的修饰符,例如 .trim,.number 和 .lazy。在 Vue 3 中,我们可以继续使用这些修饰符,而且还可以自定义修饰符。帅吧✌️
- 首先声明 modelModifiers prop,它的默认值是一个空对象:
modelModifiers?: { capitalize: boolean }注意这里组件的modelModifiersprop 包含了capitalize且其值为true,它在模板中的v-model绑定时会用到。
可以检查 modelModifiers 对象的键,并编写一个处理函数:每次
<input>元素触发 input 事件时将值的首字母大写:if (props.modelModifiers?.capitalize) { value = value.charAt(0).toUpperCase() + value.slice(1) }- 使用时直接在 v-model 后面加
.capitalize:
<Form v-model.capitalize="name" v-model:percent.number="percent" />对于又有参数又有修饰符的 v-model 绑定,生成的 prop 名将是:参数名 + 修饰符名。
测试 v-model
基本套路是通过 props 渲染选项传入 prop 初始值,然后断言这些初始值是否正确。接下来更改组件值,断言是否派发了相应 update 事件:
import { render, fireEvent } from '@testing-library/vue' import Form from './Form.vue' test('v-model', async () => { const { emitted, getByRole, rerender } = render(Form, { props: { modelValue: '', modelModifiers: { capitalize: true } } }) const nameInput = getByRole('textbox') expect(nameInput).toHaveValue('') await fireEvent.update(nameInput, 'something') expect(emitted()['update:modelValue'][0]).toEqual(['Something']) await rerender({ percent: 0 }) const percentInput = getByRole('spinbutton') expect(percentInput).toHaveValue(0) await fireEvent.update(percentInput, '24') expect(emitted()['update:percent'][0]).toEqual([0.24]) })Vue Testing Library v6.1 新增了rerender渲染器方法, 类似 Vue Test Utils 中的setProps包裹器方法,我们可以在单个测试用例中动态更改props渲染选项。
评论 (0)