vue3:组件中,v-model的区别(新版)

16 阅读2分钟

一、v-model 传值的核心本质

v-model语法糖,本质是「父组件传值 + 子组件触发更新事件」的组合:

  • 对原生表单元素:v-model="xxx":value="xxx" @input="xxx = $event.target.value"
  • 对自定义组件:v-model="xxx":modelValue="xxx" @update:modelValue="xxx = $event""update:modelValue"仅仅是个事件名而已

这个规则是所有用法的基础,记住它就能灵活应对各种场景。

二、场景:父子组件的 v-model 传值(核心重点)

1、Vue的单向数据流,禁止子组件直接修改 props(包括用 v-model 绑定)因为props是只读的;

2、子组件不能直接给 props.modelValue 绑 v-model,必须通过「内部变量(ref) / 计算属性(computed)」中转,因为v-model 是 语法糖

默认传值(modelValue)

<!-- 父组件 Father.vue -->
<template>
  <input type="number" v-model="msg">
  <Child v-model="msg"></Child>
</template>
<script setup>
import { ref } from 'vue'
import Child from './components/test.vue'
const msg = ref(100)
// 仿照接口的异步数据
setTimeout(() => {
  msg.value = 2000
}, 1000)
</script>
<!-- 子组件 Child.vue -->
<template>
    <input 
    v-model="innerValue"
  />
</template>
<script setup>
import { ref, watch } from 'vue';

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

// 监听外部变量父组件props变化 → 同步到本地变量(父 → 子)
// immediate: true 确保初始化时也能触发(如果是异步数据必须加,不然初始化数据一直是100,同步数据可不加)
watch(() => props.modelValue, (newVal) => {
  innerValue.value = newVal
}, { immediate: true })

//  监听本地变量子组件的数据变化 → 触发emit通知父组件(子 → 父)
watch(innerValue, (newVal) => {
  emit('update:modelValue', newVal)
})
</script>

Tips: 因为仿照接口的异步数据,所以16-18行代码必须加),通过v-model和watch的结合,父子组件的值,双向绑定效果就出来了。

<!-- 父组件 Father.vue -->
<template>
  <div>
      <Test v-model="message"></Test>
      <span>父传子:<input type="number" v-model="message"></span>
  </div>
</template>

<script setup >
import { ref } from 'vue';
import Test from './components/test.vue'
const message = ref(100);
setTimeout(() => {
  message.value = 20000
},1000);
</script>
<!-- 子组件 Child.vue -->
<template>
  <div>
    子传父:<input type="number" v-model="message">
  </div>
</template>

<script setup>
import { computed, ref, watch } from 'vue';
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
  modelValue: Number
})
const emits = defineEmits(['update:modelValue']) 
// 不能直接修改computed的值,会报错:[Vue warn] Write operation failed: computed value is readonly
// const message = computed(() => props.modelValue) // 报错
const message = computed({
  get(){
    return props.modelValue
  },
  set(val){
    emits('update:modelValue', val)
  }
})
</script>

Tips: computedget 方法本身就会监听依赖的响应式变化(包括异步数据),只要依赖变了,computed 的值就会自动更新,所以不用像watch那么复杂监听2次。

<!-- 父组件 -->
<template>
  <el-button type="primary" @click="openInnerDialog">
    Open the inner Dialog
  </el-button>
  <InnerDialog v-model="innerVisible" />
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import InnerDialog from './components/test.vue'

const innerVisible = ref(false)
const openInnerDialog = () => {
  innerVisible.value = true
}

</script>
<!-- 子组件 -->
<template>
  <el-dialog
    v-model="visible"
    width="500"
    title="Inner Dialog"
    append-to-body
  >
    <span>This is the inner Dialog</span>
  </el-dialog>
</template>

<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const visible = computed({
  get: () => props.modelValue, 
  set: (val) => emit('update:modelValue', val)
})

</script>

Tips:弹框组件的常见用法:props和emit(缺点:父组件,每个入口触发 emit)、ref(缺点:父组件直接操作子组件实例,耦合性高,不符合 “单向数据流”、Vue3 中需手动 defineExpose 暴露方法很麻烦)等。