vue3中自定一个组件并且能够用v-model对自定义组件进行数据的双向绑定
1. 基础用法
在 Vue3 中,v-model
在组件上的使用有了更灵活的方式。默认情况下,v-model
使用 modelValue
作为 prop,update:modelValue
作为事件。
1.1 基本示例
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<!-- 父组件使用 -->
<template>
<CustomInput v-model="searchText" />
</template>
<script setup>
import { ref } from 'vue'
const searchText = ref('')
</script>
1.2 使用 computed 实现双向绑定
<!-- CustomInput.vue -->
<template>
<input v-model="inputValue" />
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
// 使用计算属性实现双向绑定
const inputValue = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
2. 自定义 v-model 名称
Vue3 允许我们在同一个组件上使用多个 v-model,每个 v-model 可以有自己的名称。
2.1 单个自定义名称
<!-- CustomField.vue -->
<template>
<input
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
<!-- 父组件使用 -->
<template>
<CustomField v-model:title="articleTitle" />
</template>
<script setup>
import { ref } from 'vue'
const articleTitle = ref('默认标题')
</script>
2.2 多个 v-model 绑定
<!-- UserForm.vue -->
<template>
<div class="form">
<input
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</div>
</template>
<script setup>
defineProps(['firstName', 'lastName'])
defineEmits(['update:firstName', 'update:lastName'])
</script>
<!-- 父组件使用 -->
<template>
<UserForm
v-model:firstName="user.firstName"
v-model:lastName="user.lastName"
/>
</template>
<script setup>
import { reactive } from 'vue'
const user = reactive({
firstName: 'John',
lastName: 'Doe'
})
</script>
3. v-model 修饰符
3.1 内置修饰符
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
</script>
<!-- 父组件使用 -->
<template>
<CustomInput v-model.trim="text" />
</template>
3.2 自定义修饰符
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="handleInput"
/>
</template>
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
const handleInput = (event) => {
let value = event.target.value
// 检查是否应用了 capitalize 修饰符
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<!-- 父组件使用 -->
<template>
<CustomInput v-model.capitalize="text" />
</template>
4. 实际应用示例
4.1 自定义数字输入组件
<!-- NumberInput.vue -->
<template>
<div class="number-input">
<button @click="decrease">-</button>
<input
type="number"
:value="modelValue"
@input="handleInput"
:step="step"
/>
<button @click="increase">+</button>
</div>
</template>
<script setup>
const props = defineProps({
modelValue: {
type: Number,
required: true
},
step: {
type: Number,
default: 1
},
min: {
type: Number,
default: -Infinity
},
max: {
type: Number,
default: Infinity
}
})
const emit = defineEmits(['update:modelValue'])
const handleInput = (event) => {
const value = Number(event.target.value)
if (isValidValue(value)) {
emit('update:modelValue', value)
}
}
const increase = () => {
const newValue = props.modelValue + props.step
if (isValidValue(newValue)) {
emit('update:modelValue', newValue)
}
}
const decrease = () => {
const newValue = props.modelValue - props.step
if (isValidValue(newValue)) {
emit('update:modelValue', newValue)
}
}
const isValidValue = (value) => {
return value >= props.min && value <= props.max
}
</script>
<!-- 父组件使用 -->
<template>
<NumberInput
v-model="quantity"
:step="1"
:min="0"
:max="100"
/>
</template>
<script setup>
import { ref } from 'vue'
const quantity = ref(1)
</script>
4.2 颜色选择器组件
<!-- ColorPicker.vue -->
<template>
<div class="color-picker">
<div
class="color-preview"
:style="{ backgroundColor: modelValue }"
></div>
<input
type="color"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
<input
type="text"
:value="modelValue"
@input="handleInput"
placeholder="#000000"
/>
</div>
</template>
<script setup>
const props = defineProps({
modelValue: {
type: String,
default: '#000000'
}
})
const emit = defineEmits(['update:modelValue'])
const handleInput = (event) => {
const value = event.target.value
// 验证是否为有效的颜色值
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
emit('update:modelValue', value)
}
}
</script>
<!-- 父组件使用 -->
<template>
<ColorPicker v-model="themeColor" />
</template>
<script setup>
import { ref } from 'vue'
const themeColor = ref('#42b883')
</script>
5. 最佳实践
-
命名规范
- 使用语义化的 prop 名称
- 保持事件名称与 prop 名称的一致性
- 使用
update:
前缀作为事件名称
-
类型检查
- 为 props 定义明确的类型
- 在必要时添加自定义验证
- 处理无效输入
-
值的验证
- 在更新值之前进行验证
- 提供适当的错误反馈
- 实现合理的默认值处理
-
性能优化
- 避免不必要的值更新
- 使用计算属性优化复杂逻辑
- 合理使用防抖和节流