目前大部分前端组件库中的组件都是通过v-model
来实现的,作用是创建双向数据绑定
,让开发者不需要手动编写事件监听器和数据更新逻辑。
下面以自定义组件多选按钮
为例,简单介绍下v-model
在vue2
和vue3
自定义组件中的使用。
vue2 中 v-model 的使用
父组件通过v-model
实现btnSelect
属性的双向数据绑定。
点击按钮,SelectBtn
子组件会给父组件emit
事件btnChange
,通知父组件当前正在点击的按钮。同时btnSelect
也会自动更新,表示当前选中了哪些按钮。
父组件
通过v-model
绑定btnSelect
属性。
js
<template>
<div class="wrap">
<p>请选择需要奖励的班级:</p>
<SelectBtn
:options="options"
v-model="btnSelect"
@btnChange="btnChange"
></SelectBtn>
<p>您选择的班级是:{{ btnSelect.length ? btnSelect : "" }}</p>
</div>
</template>
<script>
import SelectBtn from "./child.vue";
export default {
components: {
SelectBtn,
},
data() {
return {
options: [
{
label: "一年级一班",
value: "1-1",
},
{
label: "一年级二班",
value: "1-2",
},
{
label: "一年级三班",
value: "1-3",
},
{
label: "一年级四班",
value: "1-4",
},
],
btnSelect: [],
};
},
methods: {
/**
* @desc 按钮点击事件
* @param {*} item 点击的按钮
* @return {*}
*/
btnChange(item) {
console.log("当前点击的按钮:", item);
console.log("v-model绑定的btnSelect:", this.btnSelect);
},
},
};
</script>
<style lang="scss" scoped>
.wrap {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 30px;
p {
text-align: left;
margin-bottom: 20px;
}
}
</style>
子组件
model
作为一个vue2
选项,用于自定义组件的 v-model
绑定。model
选项接受一个对象,该对象有两个属性:prop
和 event
。prop
用于指定 v-model
绑定的属性,event
用于指定触发 v-model
更新的事件。
this.$emit("v-model绑定的事件", y)
更新事件触发,父组件中v-model
绑定的变量值即更新为y
js
// 多选按钮
<template>
<div class="select-box">
<div
:class="
btnSelect.includes(item.value)
? 'btn-active select-btn-item'
: 'select-btn-item'
"
v-for="(item, index) in options"
:key="index"
@click="handleBtnSelect(item)"
>
{{ item.label }}
</div>
</div>
</template>
<script>
export default {
model: {
prop: "btnSelect", // v-model绑定的变量
event: "updateBtnSelect", // v-model绑定的事件
},
props: {
options: {
type: Array,
default: () => [],
},
btnSelect: {
type: Array,
default: () => [],
},
},
methods: {
/**
* @desc 按钮点击,更新v-model绑定的变量btnSelect,同时emit事件btnChange,通知父组件当前正在点击的按钮项
* @param {*} item
* @return {*}
*/
handleBtnSelect(item) {
let selectIndex = this.btnSelect.findIndex((ele) => ele === item.value);
// 按钮多选逻辑处理
if (selectIndex > -1) {
this.$emit("updateBtnSelect", [
...this.btnSelect.slice(0, selectIndex),
...this.btnSelect.slice(selectIndex + 1),
]);
} else {
this.$emit("updateBtnSelect", [...this.btnSelect, item.value]);
}
// 通知父组件当前正在点击的按钮项
this.$emit("btnChange", item);
},
},
};
</script>
<style lang="scss" scoped>
.select-box {
display: flex;
color: #4d4d4d;
.select-btn-item {
border-radius: 2px;
cursor: pointer;
padding: 10px 20px;
border: 1px solid #e0e0e0;
margin-right: 10px;
}
.btn-active {
color: white;
background: #4d99f9;
border: 1px solid #4d99f9;
}
}
</style>
vue3 中 v-model 的使用
点击按钮,SelectBtn
子组件会给父组件emit
事件btnChange
,通知父组件当前正在点击的按钮。同时btnSelect
也会自动更新,表示当前选中了哪些按钮。
父组件
通过v-model:xxx="yyy"
或者v-model="yyy"
实现属性的双向数据绑定。
如果为v-model:btnSelect="btnSelect"
,父组件绑定的属性是btnSelect
,同时子组件接收到的props
变量为btnSelect
如果为v-model="btnSelect"
,父组件绑定的属性是btnSelect
,同时子组件接收到的props
变量为默认值modelValue
js
<template>
<div class="wrap">
<p>请选择多选按钮:</p>
<SelectBtn
:options="options"
v-model:btnSelect="btnSelect"
@btnChange="btnChange"
></SelectBtn>
<p>v-model绑定的btnSelect:{{ btnSelect.length ? btnSelect : "" }}</p>
</div>
</template>
<script setup>
import SelectBtn from "./child.vue";
import { ref } from "vue";
const options = ref([
{
label: "一年级一班",
value: "1-1"
},
{
label: "一年级二班",
value: "1-2"
},
{
label: "一年级三班",
value: "1-3"
},
{
label: "一年级四班",
value: "1-4"
}
]);
const btnSelect = ref([]); // v-model绑定的变量
/**
* @desc 按钮点击事件
* @param {*} item 当前点击的按钮
* @return {*}
*/
const btnChange = (item) => {
console.log("当前点击的按钮:", item);
console.log("v-model绑定的btnSelect:", btnSelect.value);
};
</script>
<style lang="scss" scoped>
.wrap {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 30px;
p {
text-align: left;
margin-bottom: 20px;
}
}
</style>
子组件
子组件接收到的v-model
绑定的props
变量由父组件决定。
子组件定义v-model
绑定的事件,格式为update:xxx
, xxx
为v-model
绑定的props
变量。emit("update:xxx", 更新值y)
事件触发,父组件中v-model
绑定变量的变量值就会更新为y
- 如果父组件中定义
v-model="btnSelect"
,那么子组件中绑定的props
变量就是modelValue
,v-model
绑定的事件就是update:modelValue
- 如果父组件中定义
v-model:btnSelect="btnSelect"
,那么子组件中绑定的props
变量就是btnSelect
,v-model
绑定的事件就是update:btnSelect
js
<template>
<div class="select-box">
<div
:class="
btnSelect.includes(item.value)
? 'btn-active select-btn-item'
: 'select-btn-item'
"
v-for="(item, index) in options"
:key="index"
@click="handleBtnSelect(item)"
>
{{ item.label }}
</div>
</div>
</template>
<script setup>
const props = defineProps({
options: {
type: Array,
default: () => []
},
btnSelect: {
type: Array,
default: () => []
}
});
// 定义v-model绑定的事件,格式为"update:v-model绑定的变量"
const emit = defineEmits(["update:btnSelect", "btnChange"]);
/**
* @desc 按钮点击事件,更新v-model绑定的变量btnSelect,同时emit事件btnChange,通知父组件当前正在点击的按钮项
* @param {*} item
* @return {*}
*/
const handleBtnSelect = (item) => {
let selectIndex = props.btnSelect.findIndex((ele) => ele === item.value);
if (selectIndex > -1) {
emit("update:btnSelect", [
...props.btnSelect.slice(0, selectIndex),
...props.btnSelect.slice(selectIndex + 1)
]);
} else {
emit("update:btnSelect", [...props.btnSelect, item.value]);
}
emit("btnChange", item);
};
</script>
<style lang="scss" scoped>
.select-box {
display: flex;
color: #4d4d4d;
.select-btn-item {
border-radius: 2px;
cursor: pointer;
padding: 10px 20px;
border: 1px solid #e0e0e0;
margin-right: 10px;
}
.btn-active {
color: white;
background: #4d99f9;
border: 1px solid #4d99f9;
}
}
</style>