组件双向绑定

@defineModel()
Introduction
组件的双向绑定:父组件的数据变化会影响到子组件;子组件的数据变化会影响到父组件
子组件使用宏 defineModel() 声明一个双向绑定属性 prop
父组件使用 v-model 关联绑定
可以双向绑定一个或多个数据
早期版本使用 v-model 实现
基本过程 Procedure
1. 子组件
使用 defineModel() 声明一个双向绑定属性 props,同样建议使用变量接收,默认是 model
const model = defineModel()
可以指定类型
const model = defineModel({type: Number, default: 0 })
声明的数据默认是 ref(),所以逻辑中使用需要指定 .value
2. 父组件
引入子组件并使用 v-model 绑定某个数据,如 count,该数据和子组件的定义的 model 建立起双向绑定
<Model v-model ="count" />
子组件对数据 model 的操作,会影响到父组件 count
父组件对数据 count 的操作,会影响到子组件 model
子组件的 model 和 父组件的 count 双向绑定
数据流
最佳体验 Best Practice
1. 子组件 - 声明时显示指定 v-model 属性 num;同样可以指定类型 type 和默认值 default
const num = defineModel('num')
const num = defineModel('num', { type: Number, default: 0 })
2. 父组件 - 使用时显示指定 v-model 属性 num 和 count 绑定
<Model v-model:num ="count" />
多个双向绑定
可以指定多个双向绑定数据;如 first, last
多个双向绑定必须指定参数
1. 子组件
const first = defineModel('first')
const last = defineModel('last')
<input type="text" v-model="first">
<input type="text" v-model="last">
2. 父组件
let user = ref({
    first: 'gl',
    last: 'cn'
})
<Model v-model:first="user.first" v-model:last="user.last" />
底层机制
使用 语法糖 规则,修改为特定属性和特定方法,实现双向绑定
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)">
Drill
[] 模态框 - 双向绑定版
详情页 DetailsView.vue → 权益保护模态框组件 Modal.vue;使用双向绑定 defineModel() 实现
围绕一个数据 isShowModal 展开
1. 权益组件 Guarantee.vue
<div class="guarantee">
  <div>guarantee</div>
  <button @click="model = true">show</button>
</div>
const model = defineModel()
2. 模态框组件 Modal.vue
<div class="modal">
  <div>modal</div>
  <button @click="model = false">close</button>
</div>
const model = defineModel()
3. 父组件 HomeView.vue
<Guarantee v-model="isShowModal"></Guarantee>
<Modal v-model="isShowModal" v-show="isShowModal"></Modal>
import { ref } from 'vue';
import Guarantee from '@/components/Guarantee.vue';
import Modal from '@/components/Modal.vue';
const isShowModal = ref(false);
数据流
Summary & Homework
Summary
子组件:defineModel()
父组件:v-model
Homework
[] 产品规格 - 使用 radio 实现
. 数据结构不同,实现的方法略有不同
1. 杯型:中杯、大杯、霸王杯
2. 冷热:少冰、正常冰、多冰
3. 糖度:三分糖、五分糖、七分糖
子组件 Specification.vue:每个规格都是一组单选
<div class="specification">
  <span>{{ item.label }}</span>
  <label class="specification-item" v-for="option in item.options" :key="option.id">
    <input type="radio" :name="item.name" :value="option.label" v-model="model" hidden>
    <span>{{ option.label }}</span>
  </label>
</div>
const model = defineModel()
const props = defineProps({
  item: {
    type: Object,
    default: {}
  }
})
参考数据节点
{
  "id": 101,
  "label": "杯型",
  "name": "cup",
  "options": [
    { "id": 1, "label": "中杯", "value": "中杯", "sel": true },
    { "id": 2, "label": "大杯", "value": "大杯", "sel": false },
    { "id": 3, "label": "超大杯", "value": "超大杯", "sel": false }
  ]
}

详情页规格组件效果

[] 甜点组件 - 使用 checkbox 实现
1. 子组件 RecoDessert.vue
. 定义 props 属性 dessert
. 额外指定 v-model 属性 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 实现
修改单选组件为组件双向绑定
确定情况下的绑定应用 - 性别
子组件的 radio 和 父组件的数据双向绑定
子组件:绑定内容、绑定选择
父组件:响应子组件的性别选择;严格意义上,仅仅是子组件和父组件的数据绑定
也可以使用自定义事件实现