双向数据绑定在前端最早用在 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
}
注意这里组件的modelModifiers
prop 包含了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)