Skip to content

1. 核心原理:v-model 的语法糖本质

在 Vue 3 中,v-model 不再是黑盒,它等同于 属性绑定 (Props) + 事件监听 (Emits)

  • 父组件写法:
    <Child v-model:initialCounter="total" />
    //等同于
    <Child 
      :initialCounter="total" 
      @update:initialCounter="val => total = val" 
    />
  • 知识点: v-model 后面可以接冒号传参数(如 :initialCounter),这意味着一个组件可以同时绑定多个 v-model

2. 现代写法:defineModel (Vue 3.4+)

这是目前最推荐的方式,它极大地简化了子组件的代码,自动处理了 props 声明和 emit 触发。

  • 子组件实现:

    JavaScript

    // 声明一个双向绑定的 ref
    const counter = defineModel('initialCounter');
    
    // 修改时,父组件同步更新,无需手动 emit
    const add = () => counter.value++;

3. 传统写法:Vue 3.4 以前的方案

在没有 defineModel 时,需要手动建立子组件内部状态与父组件 props 的联系。

⚠️ 错误做法: const c = ref(props.prop) (会导致数据流断裂,仅初始化一次)。

方案代码片段 (子组件)适用场景
computedconst c = computed({ get:()=>props.p, set:(v)=>emit('update:p', v) })最推荐。逻辑清晰,支持在 set 中做数据校验。
watchwatch(()=>props.p, (v)=>local.value=v)适合在数据变化时需要执行异步操作或复杂逻辑。
toRefconst c = toRef(props, 'p')保持引用。但注意:由于单向数据流,子组件不能直接修改 c.value

4. 经验总结

  • 单向数据流:无论哪种方案,最终目的都是为了遵循“父变子随,子改父定”的原则。

  • 数据校验:如果需要对子组件输入的值做限制(如最大最小值),建议使用 computedsetwatch 模式进行拦截,校验通过后再发送 emit

基于 Obsidian + VitePress 构建