在 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.ts
import { computed, getCurrentInstance } from 'vue'
export function useVModel(props, name, emit) {
const cEmit = emit || getCurrentInstance()?.emit
return 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-name
v-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: `
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)" />
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)" />
`
})