v-model 与组合式函数是 vue 3 表单开发的天然搭档:前者实现视图与数据双向绑定,后者封装校验、请求、防抖等逻辑,使组件保持声明式与可复用性。

v-model 和组合式函数(Composables)在 Vue 3 中是天然搭档:前者负责视图与数据的自动同步,后者负责把表单相关的状态、校验、请求、防抖等逻辑抽离复用。两者结合,能让表单组件从“写满业务代码的容器”变成“干净声明式的壳”。
一、v-model 是表单的“连接线”,不是逻辑本身
v-model 本质是语法糖,它不处理校验、不发起请求、不管理 loading 状态——它只做一件事:让一个响应式变量和一个输入控件保持双向同步。
比如:
<template><input v-model="form.name"><span v-if="errors.name">{{ errors.name }}</span>
</template>
这里 form.name 是谁管?errors.name 怎么来?提交时怎么校验?这些都不该塞在组件里硬写,而应交给组合式函数统一组织。
二、用组合式函数接管表单核心能力
一个典型的表单逻辑组合式函数,通常包含:
- 响应式表单数据(
ref或reactive) - 校验规则与错误信息(
computed+watch触发校验) - 提交方法(含 loading、防抖、接口调用、错误提示)
- 重置、清空、设置初始值等辅助方法
示例:useLoginForm.ts
import { ref, reactive, computed, watch } from 'vue'
export function useLoginForm() {
const form = reactive({
username: '',
password: ''
})
const errors = reactive({
username: '',
password: ''
})
const isValid = computed(() => {
return form.username.trim() && form.password.length >= 6
})
const validate = () => {
errors.username = form.username ? '' : '用户名不能为空'
errors.password = form.password.length >= 6 ? '' : '密码至少6位'
}
watch(() => [form.username, form.password], validate, { immediate: true })
const submit = async () => {
validate()
if (!isValid.value) return
// 这里可接入 useApi、useToast 等其他 Composable
console.log('提交:', form)
}
const reset = () => {
Object.assign(form, { username: '', password: '' })
Object.keys(errors).forEach(key => (errors[key as keyof typeof errors] = ''))
}
return {
form,
errors,
isValid,
submit,
reset
}
}
在组件中直接解构使用:
<script setup>
import { useLoginForm } from './composables/useLoginForm'
const { form, errors, isValid, submit, reset } = useLoginForm()
</script><template><form>
<input v-model="form.username" placeholder="用户名"><span class="error">{{ errors.username }}</span>
<input v-model="form.password" type="password" placeholder="密码"><span class="error">{{ errors.password }}</span>
<button :disabled="!isValid">登录</button>
</form>
</template>
✅ 表单 UI 只关注结构和绑定
✅ 所有逻辑都在 useLoginForm 里,可测试、可复用、可单独调试
三、配合自定义组件时,v-model + Composable 更灵活
当你封装 <custominput></custominput> 这类组件时,v-model 提供标准接口,组合式函数提供内部行为:
- 子组件接收
modelValue,触发update:modelValue - 组合式函数在子组件内部管理自己的
value、debounce、format、validate等逻辑
例如 useDebouncedInput.ts:
import { ref, watch, nextTick } from 'vue'
import { debounce } from 'lodash-es'
export function useDebouncedInput(initialValue = '', delay = 300) {
const innerValue = ref(initialValue)
const emitUpdate = ref void>(() => {})
const update = debounce((v: string) => {
emitUpdate.value(v)
}, delay)
watch(innerValue, update)
const setValue = (v: string) => {
innerValue.value = v
}
return {
innerValue,
setValue,
bind: (fn: (v: string) => void) => { emitUpdate.value = fn }
}
}
在 <custominput></custominput> 中使用:
<script setup>
import { defineProps, defineEmits, onMounted } from 'vue'
import { useDebouncedInput } from './composables/useDebouncedInput'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const { innerValue, bind } = useDebouncedInput(props.modelValue)
onMounted(() => {
bind((v) => emit('update:modelValue', v))
})
</script><template><input :value="innerValue"> innerValue = e.target.value" />
</template>
这样,父组件仍用 v-model,子组件却拥有了防抖能力 —— v-model 负责契约,Composable 负责实现。
四、进阶:多个 v-model + 多个 Composable 协同
比如一个日期范围筛选组件:
<daterangepicker v-model:start="query.start" v-model:end="query.end"></daterangepicker>
它内部可以分别调用:
-
useDateValidator()处理单个日期格式校验 -
useDateRangeSync()确保 end 不早于 start -
useDatePersistence()自动存到 localStorage
每个能力独立、可开关、可替换,不耦合 UI,也不污染父组件。
不复杂但容易忽略
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










