说说父子组件双向绑定defineModel?

345 阅读2分钟

defineModel 是 Vue 3.4 引入的一个组合式语法糖,用于更简洁地在子组件中 声明并使用 v-model 绑定的 props 和事件,是对传统 props + emit('update:modelValue') 模式的封装。

defineModel 是在子组件中声明 v-model 的 语法糖,用于代替 props 和 emit 的手动处理。

用法演示 👀

<!-- 父组件 -->
<MyInput v-model="msg" />
<!-- 子组件:MyInput.vue -->
<script setup>
const model = defineModel() // 相当于 props.modelValue + emit('update:modelValue')
</script>

<template>
  <input v-model="model" />
</template>

是不是比以前的写法简单多了?

Vue3.4 之前怎么写 🔧

// 子组件
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const model = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})

defineModel的本质原理🧠

Vue 编译器在编译 script setup 时,会自动将 defineModel() :

  • 生成一个 props.modelValue 的访问 getter

  • 生成一个 emit('update:modelValue') 的 setter

  • 自动构造 computed,并返回一个响应式绑定

相当于你写了下面这些东西:

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const model = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})

多个 v-model(命名 model)🧩

<!-- 父组件 -->
<MyInput v-model:title="title" v-model:content="content" />
// 子组件
const title = defineModel('title')
const content = defineModel('content')

Vue 会自动映射到:

  • props.title / props.content
  • emit('update:title') / emit('update:content')

类型支持 💡

你可以显式指定类型:

const model = defineModel<string>()
const title = defineModel<string>('title')

注意事项 ⚠️

项目说明
Vue 版本defineModel 仅在 Vue 3.4+ 中可用
编译器支持依赖 Vue SFC 的编译器支持,不能在普通 JS 文件中使用
必须配合 <script setup>不能在普通 <script> 中使用
不是运行时 API它是编译期语法糖,运行时并不存在 defineModel 函数

代码分析 🧐

可以借助 Vue 官方提供的 SFC Playground 编译结果来看 defineModel 的编译产物。

编译后的 setup 等效代码(简化模拟)⚙️

export default {
  props: {
    modelValue: {
      type: String,
    }
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const model = computed({
      get: () => props.modelValue,
      set: (value) => emit('update:modelValue', value)
    })

    return {
      model
    }
  }
}

你可以看到:

  • defineModel() 被转化为对 props.modelValue 的 computed 包装;
  • 自动注册了 props.modelValue 和 emits.update:modelValue;
  • 最终返回的 model 变量,是可以直接绑定在 v-model="model" 上的响应式引用。
export default {
  props: {
    title: String,
    content: String
  },
  emits: ['update:title', 'update:content'],
  setup(props, { emit }) {
    const title = computed({
      get: () => props.title,
      set: (val) => emit('update:title', val)
    })

    const content = computed({
      get: () => props.content,
      set: (val) => emit('update:content', val)
    })

    return {
      title,
      content
    }
  }
}

深入理解:defineModel 的本质 🛠

defineModel(name?: string) =>
  computed({
    get: () => props[name || 'modelValue'],
    set: (val) => emit(`update:${name || 'modelValue'}`, val)
  })

并且它隐式注册了 props 和 emits,无需你手动写。