在 Vue 中可以使用 v-model 指令来用作对表单数据的双向绑定。此指令的使用仅局限于以下几种情况:
<input><textarea><select>- components
可以使用以下几种修饰符:
- .lazy 使用 change 事件而非 input
- .number 将输入的字符串转为数字
- .trim 将输入框的内容进行 trim 操作
在表单中使用
这个指令其实是一种语法糖
<template><input :value="inputValue" @input="$emit('input', inputValue = $event.target.value)" /></template><script>import { ref } from 'vue'export default {name: 'Foo',setup() {const inputValue = ref('')return {inputValue}}}</script>
<template><input v-model="inputValue" /></template><script>import { ref } from 'vue'export default {name: 'Foo',setup() {const inputValue = ref('')return {inputValue}}}</script>
在组件中使用
常规使用方式
父级组件
<template><custom-input:model-value="searchText"@update:model-value="searchText = $event"></custom-input><custom-input v-model="searchText" /></template>
子组件
<template><input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"</template>
或者使用计算属性,将 value 设置为 getter 和 setter
app.component('custom-input', {props: ['modelValue'],emits: ['update:modelValue'],template: `<input v-model="value">`,computed: {value: {get() {return this.modelValue},set(value) {this.$emit('update:modelValue', value)}}}})
创建新的响应式数据来模拟
父级组件:
<template><child v-model:myData="text"></child><p>{{ text }}</p></template><script setup>import Child from './Child.vue'import { ref } from 'vue'const props = defineProps({msg: String})const text = ref('')</script>
子级组件:
<template><div><input type="text" v-model="source"></div></template><script setup>import { defineProps, watch, ref } from 'vue'const props = defineProps({myData: String})const emit = defineEmits(['update:myData'])const source = ref(props.myData)watch(source, () => {emit('update:myData', source.value)})</script>
抽离成 hooks
将其抽象为一个单独的自定义 hooks:
// useVModel.tsimport { computed, getCurrentInstance } from 'vue'export function useVModel(props, name, emit) {const cEmit = emit || getCurrentInstance()?.emitreturn computed({get() {return props[name]},set(v) {if (cEmit) {emit(`update:${name}`, v)}}})}
这样的话就可以在自组件中直接使用 v-model。也可以实现数据在多级组件中传递(parent -> child -> grandChild),可以进行三级或以上的参数传递。
// 父级组件<template><child v-model:myData="counter" /></template>// 子组件<template><input type="text" v-model="data" /></template><script lang="ts">import { useVModel } from './hooks/useVModel'import { defineComponent } from 'vue'export default defineComponent({props: {myData: String},setup(props, { emit }) {const data = useVModel(props, 'myData', emit)return {data}}})</script>
多层传递
<!-- 父级组件 --><template><div><h2>Parent</h2><p>val: {{ data }}</p><button @click="name += counter">set title</button><br /><child v-model:myData="data" /></div></template><script setup lang="ts">import { ref, watch } from 'vue'import Child from './child.vue'const data = ref('hello')watch(data, (val) => {console.log(val)})</script><!-- 子级组件 --><template><div><grand-child v-model:myData="data"></grand-child></div></template><script lang="ts">import { defineComponent } from 'vue'import GrandChild from './grandChild.vue'import { useVModel } from './hooks/useVModel'export default defineComponent({name: 'Child',props: {myData: String},components: {GrandChild},setup(props, { emit }) {const data = useVModel(props, 'myData', emit)return {data}}})</script><!-- 孙级组件 --><template><div><input type="text" v-model:myData="data"></div></template><script lang="ts">import { useVModel } from './hooks/useVModel'import { defineComponent } from 'vue'export default defineComponent({name: 'grandChild',props: {myData: String},setup(props, { emit }) {const data = useVModel(props, 'myData', emit)return {data}}})</script>
使用多个 v-model 绑定
<user-namev-model:first-name="firstName"v-model:last-name="lastName"></user-name>
app.component('user-name', {props: {firstName: String,lastName: String},emits: ['update:firstName', 'update:lastName'],template: `<inputtype="text":value="firstName"@input="$emit('update:firstName', $event.target.value)" /><inputtype="text":value="lastName"@input="$emit('update:lastName', $event.target.value)" />`})
