先记住这个答案
传统写法中,不带参数的组件 v-model 修饰符通过 modelModifiers prop 传入,例如 v-model.capitalize 会得到 { capitalize: true };v-model:title.trim 对应 titleModifiers。子组件在发出 update:modelValue 或 update:title 前读取这些标记并转换候选值。修饰符不是 Vue 自动识别的任意行为,组件只应实现文档化的集合,并为 prop 提供对象默认值。Vue 3.4+ 使用 defineModel 时可以取得 modifiers,并通过 set 转换返回值,但底层仍是 prop 与更新事件契约。
- 默认通道使用 modelModifiers
- 命名通道使用参数名加 Modifiers
- 转换应发生在 emit 候选值之前
组件负责解释自定义名称
内置表单元素了解 trim、number 和 lazy,但自定义组件面对 capitalize、currency 等名称时不会自动执行转换。父级只把标记传下,子级需要在值流入父状态前实现可预测规则。组件文档还要说明转换发生在输入、失焦还是提交阶段,使父级能判断收到的是草稿还是规范值。
未知修饰符最好忽略并在类型或文档层阻止,而不是动态调用同名函数。涉及货币、日期或语言的转换应考虑 locale、精度与无效输入,简单字符串示例不能直接推广为通用解析器。
在 emit 前建立纯转换函数
下例声明 modelModifiers,并把首字母大写写成无副作用函数。每次输入从事件目标得到原始值,根据开关生成 next,再发送标准更新事件。父级关闭修饰符时原值保持不变。
实际输入还要处理中文输入法组合过程:composition 尚未结束时过早改写 value 可能破坏候选文本。复杂控件可先维护草稿,在 compositionend、blur 或明确提交时再规范化。
import { defineComponent, h, type PropType } from 'vue'
const CapitalizedInput = defineComponent({
props: {
modelValue: { type: String, required: true },
modelModifiers: {
type: Object as PropType<{ capitalize?: boolean }>,
default: () => ({})
}
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const normalize = (value: string) => props.modelModifiers.capitalize
? value.charAt(0).toUpperCase() + value.slice(1)
: value
return () => h('input', {
value: props.modelValue,
onInput: (e: Event) => emit('update:modelValue', normalize((e.target as HTMLInputElement).value))
})
}
})父级写 v-model.capitalize 时,输入 hello 会发出 Hello;普通 v-model 发出 hello。默认工厂避免组件实例共享可变对象。
命名通道与 defineModel 只是不同入口
v-model:title.capitalize 会把修饰符放入 titleModifiers,事件仍是 update:title。defineModel("title") 可返回模型 ref 与 modifiers,并通过 set 选项转换赋值;这种写法需要 Vue 3.4 以上编译器支持。
库组件若要支持旧编译器,应继续发布明确的 props/emits 运行时契约,或在 peerDependencies 中写清最低版本。源码使用宏并不表示运行时 Vue 单独升级就能识别,构建插件和编译器版本也必须匹配。
容易答错的地方
- 把 modelModifiers 当成字符串数组
- 编译结果是以修饰符名为键、布尔值为值的对象。按键读取更清楚,也方便 TypeScript 限定支持集合。
- 转换后直接写回 prop
- 转换的结果应通过 update 事件交给父级,不能赋值 props.modelValue。父级拒绝或再次格式化时,最终显示仍由传回的 prop 决定。
面试官还会怎么问?
怎样支持 v-model:title.capitalize?
声明 title prop、titleModifiers prop 和 update:title 事件,在该事件发出前读取 titleModifiers.capitalize。默认通道的 model 前缀随参数名一起变化。
defineModel 的 set 可以返回 Promise 吗?
模型 setter 应同步返回要发出的值,异步验证应放入显式流程并处理竞态。让普通 v-model 更新隐式等待网络会使输入和父状态关系难以预测。
修饰符适合做服务端校验吗?
不适合。修饰符更适合本地、确定性的轻量转换;服务端校验要有 loading、错误和取消状态,应使用明确事件或表单提交动作。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。