Vue2's v-model defaults to: value and @input
Vue3's v-model defaults to: modelValue and @update:modelValue
So if you want to support the use of v-model by encapsulating components, using v-model syntactic sugar is undoubtedly the best choice
The child component definition uses modelValue to receive the value passed by the parent component, and defines the event update:modelValue to notify the parent component to change something
Example use: encapsulating subcomponents Usage: cp-radio-btn
//子组件 cp-radio-btn
<script setup lang="ts">
// 通过v-model双向绑定实现计数器
const props = defineProps<{
modelValue: number
}>()
const emit = defineEmits<{
(e: 'update:modelValue', count: number): void
}>()
const btnClick = () => {
emit('update:modelValue', props.modelValue + 10)
}
</script>
<template>
<div class="cp-radio-btn">
计数器:{
{ modelValue }} <br />
<button @click="btnClick">修改count</button>
//<button @click="$emit('update:modelValue', modelValue + 2)">修改count</button>
</div>
</template>
<style lang="scss" scoped></style>
parent component patient - use child component cp-radio-btn
Use modelValue to pass data to subcomponents, and define events notified by subcomponents @update:modelValue to do things
//父组件 patient
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const count = ref(0)
const updateCount = (num: number) => {
count.value = num
}
</script>
<cp-radio-btn :modelValue="count" @update:modelValue="updateCount"></cp-radio-btn>
//<cp-radio-btn :modelValue="count" @update:modelValue="count = $event"></cp-radio-btn>
This completes the two-way binding of data;
Here, :modelValue="count" @update:modelValue="updateCount" can be abbreviated as v-model="count"
Can achieve the same effect as the above code
<cp-radio-btn v-model="count"></cp-radio-btn>
The above article began to say that Vue3's v-model is parsed into: modelValue and @update:modelValue by default. Let me talk about it below. If you want to modify the default value and event, how to modify it?
For example: when changing the name of the value event here (here, use OtherName as an example)
After using v-model, you need to add: OtherName
<cp-radio-btn v-model:OtherName="count"></cp-radio-btn>
子组件的传值以及事件都需要改为自定义的名称
<script setup lang="ts">
// 通过v-model双向绑定实现计数器
const props = defineProps<{
OtherName: number
}>()
const emit = defineEmits<{
(e: 'update:OtherName', OtherName: number): void
}>()
const btnClick = () => {
emit('update:OtherName', props.OtherName + 10)
}
</script>
<template>
<div class="cp-radio-btn">
计数器:{
{ OtherName }} <br />
<button @click="btnClick">修改OtherName</button>
</div>
</template>
<style lang="scss" scoped></style>
Encapsulate a custom radio button component cp-radio-btn to meet the following requirements
Post code:
<script setup lang="ts">
defineProps<{
options: { label: string; value: number | string }[]
modelValue?: number | string
}>()
defineEmits<{
(e: 'update:modelValue', value: number | string): void
}>()
</script>
<template>
<div class="cp-radio-btn">
<a
class="item"
href="javascript:;"
v-for="item in options"
:key="item.value"
:class="{ active: modelValue == item.value }"
@click="$emit('update:modelValue', item.value)"
>{
{ item.label }}</a
>
</div>
</template>
<style lang="scss" scoped>
.cp-radio-btn {
display: flex;
flex-wrap: wrap;
.item {
height: 32px;
min-width: 60px;
line-height: 30px;
padding: 0 14px;
text-align: center;
border: 1px solid var(--cp-bg);
background-color: var(--cp-bg);
margin-right: 10px;
box-sizing: border-box;
color: var(--cp-text2);
margin-bottom: 10px;
border-radius: 4px;
transition: all 0.3s;
&.active {
border-color: var(--cp-primary);
background-color: var(--cp-plain);
}
}
}
</style>
Use components:
<cp-radio-btn :options="timeOptions" v-model="form.illnessTime" />
Use vant's van-action-sheet action panel component to encapsulate a payment component cp-pay-sheet
cp-pay-sheet
<script setup lang="ts">
import { showToast, showLoadingToast } from 'vant'
import { ref } from 'vue'
import { getConsultOrderPayUrl } from '@/services/consult'
const props = defineProps<{
orderId: string
actualPayment: number
onClose?: () => void
show: boolean
}>()
const emit = defineEmits<{
(e: 'update:show', val: boolean): void
}>()
const paymentMethod = ref<0 | 1>()
// 跳转支付
const pay = async () => {
if (paymentMethod.value === undefined) return showToast('请选择支付方式')
showLoadingToast({ message: '跳转支付', duration: 0 })
const res = await getConsultOrderPayUrl({
orderId: props.orderId,
paymentMethod: paymentMethod.value,
payCallback: 'http://localhost:5173/room'
})
window.location.href = res.data.payUrl
}
</script>
<template>
<!-- 支付方式弹窗 -->
<van-action-sheet
:show="show"
@update:show="emit('update:show', $event)"
title="选择支付方式"
:close-on-popstate="false"
:closeable="false"
:before-close="onClose"
>
<div class="pay-type">
<p class="amount">¥20.00</p>
<van-cell-group>
<van-cell title="微信支付" @click="paymentMethod = 0">
<template #icon><cp-icon name="consult-wechat" /></template>
<template #extra><van-checkbox :checked="paymentMethod == 0" /></template>
</van-cell>
<van-cell title="支付宝支付" @click="paymentMethod = 1">
<template #icon><cp-icon name="consult-alipay" /></template>
<template #extra><van-checkbox :checked="paymentMethod == 1" /></template>
</van-cell>
</van-cell-group>
<div class="btn">
<van-button type="primary" round block @click="pay">立即支付</van-button>
</div>
</div>
</van-action-sheet>
</template>
<style lang="scss" scoped>
.pay-type {
.amount {
padding: 20px;
text-align: center;
font-size: 16px;
font-weight: bold;
}
.btn {
padding: 15px;
}
.van-cell {
align-items: center;
.icon-page {
margin-right: 10px;
font-size: 18px;
margin-top: 6px;
}
.van-checkbox :deep(.van-checkbox__icon) {
font-size: 16px;
}
}
}
</style>
use components
<cp-pay-sheet
v-model:show="show"
:order-id="orderId"
:actual-payment="payInfo.actualPayment"
:on-close="onClose"
></cp-pay-sheet>