组件双向绑定

@defineModel()
使用宏defineModel()方法实现组件的双向绑定:父组件的数据变化会影响到子组件;子组件的数据变化会影响到父组件
用来声明一个双向绑定 prop,通过父组件的 v-model 来使用
可以双向绑定一个或多个数据
更多细节,请访问 指南 - 组件 v-modelAPI - defineModel()
基本过程 Procedure

你声明我绑定

子组件 - Child.vue

使用 defineModel() 声明一个双向绑定数据,使用变量接收。如 model,可以是任意合法标识符,并使用该数据

声明的是 ref 数据,所以逻辑中使用需要指定 model.value

子组件对数据的操作如 updata() 方法将影响到父组件绑定的数据

const model = defineModel()
const update = () => model.value++  
<div @click="update">{{ model }}</div>
父组件 - Parent.vue

使用 v-model 绑定某个数据,如 count,该数据和子组件的 model 建立起双向绑定

父组件对数据的操作,如自增方法也会影响到子组件的数据

import { ref } from 'vue';
import Model from './components/Child.vue';
let count = ref(0)  
<div @click="count++">{{ count }}</div>
<Model v-model ="count" />
v-model 属性
最佳体验 - Best Practice
子组件 - 声明时使用属性 num;甚至可以指定类型 type 和默认值 default
const num = defineModel('num')
const num = defineModel('num', { type: Number, default: 0 })
父组件 - 使用参数 num 和 它的 count 绑定
<Model v-model:num ="count" />
多个双向绑定
可以指定多个双向绑定数据;如 first, last
多个双向绑定必须指定参数
子组件
const first = defineModel('first')
const last = defineModel('last')
<input type="text" v-model="first">
<input type="text" v-model="last">
父组件
let user = ref({
    first: 'gl',
    last: 'cn'
})
<Model v-model:first="user.first" v-model:last="user.last" />
[] 甜点组件 - 使用 checkbox 实现
1. 子组件 RecoDessert.vue
. 定义属性 dessert
. 定义绑定 dessertSelected
const props = defineProps(['dessert'])

const dessertSelected = defineModel('dessertSelected', { default: [] })
<div class="reco-dessert">
  <div class="item" v-for="(item, ind) in dessert" :key="item.id">
    <input class="cb" type="checkbox" name="dessert" :id="item.id" :value="item.title" v-model="dessertSelected">
    <label :for="item.id">
      <img class="img" :src="`https://glpla.github.io/utils/${item.img}`" alt="">
      <p>{{ item.title }}</p>
      <div class="price">¥{{ item.priceOrignal }}</div>
    </label>
  </div>
</div>
2. 父组件 DetailView.vue
使用本地数据或在线数据
import RecoDessert from '@/components/RecoDessert.vue';

const goods = ref({})
const goodsSelected = ref({
  dessert: []
})

<RecoDessert :dessert="goods.dessert" v-model:dessertSelected="goodsSelected.dessert" />
[] 产品规格 - 使用 radio 实现
1. 杯型:中杯、大杯、霸王杯
2. 冷热:少冰、正常冰、多冰
3. 糖度:三分糖、五分糖、七分糖
每个规格都是一组单选
封装为组件
通过数据绑定实现对应的三种场景
[] 性别设置/个人中心 - 使用 radio 实现
修改单选组件为组件双向绑定
确定情况下的绑定应用 - 性别
子组件的 radio 和 父组件的数据双向绑定
子组件:绑定内容、绑定选择
父组件:响应子组件的性别选择;严格意义上,仅仅是子组件和父组件的数据绑定
也可以使用自定义事件实现
底层机制
使用 语法糖 规则,修改为特定属性和特定方法,实现双向绑定
prop 名称将默认为 "modelValue"
单属性双向绑定
[] 点赞组件 - 双向绑定版
调整后的子组件 - 逻辑
const props = defineProps({
    modelValue: Number
})
const emit = defineEmits(['update:modelValue'])
const incLike = () => {
    emit('update:modelValue', props.modelValue + 1)
}
调整后的子组件 - 结构
<div @click="incLike"><span class="fa fa-heart"></span> {{ modelValue }}</div>
调整后的父组件 - 逻辑
let msg = ref(0)
调整后的父组件 - 结构
<List v-model="msg"></List>
多属性双向绑定
使用修饰符区分不同的属性;使用冒号:不是点.
父组件
<List v-model:msg="msg" v-model:tips="tips"></List>
子组件
const props = defineProps(['msg', 'tips'])
const emits = defineEmits(['update:msg', 'update:tips'])
<input type="text" :value="msg" @input="$emit('update:msg', $event.target.value)">
<input type="text" :value="tips" @input="$emit('update:tips', $event.target.value)">