【vue2、vue3】v-model在自定义组件的使用

目前大部分前端组件库中的组件都是通过v-model来实现的,作用是创建双向数据绑定,让开发者不需要手动编写事件监听器和数据更新逻辑。

下面以自定义组件多选按钮为例,简单介绍下v-modelvue2vue3自定义组件中的使用。

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 选项接受一个对象,该对象有两个属性:propeventprop 用于指定 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, xxxv-model绑定的props变量。emit("update:xxx", 更新值y)事件触发,父组件中v-model绑定变量的变量值就会更新为y

  • 如果父组件中定义v-model="btnSelect",那么子组件中绑定的props变量就是modelValuev-model绑定的事件就是update:modelValue
  • 如果父组件中定义v-model:btnSelect="btnSelect",那么子组件中绑定的props变量就是btnSelectv-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>
相关推荐
摸鱼大侠想挣钱10 分钟前
flex居中布局引起滚动溢出问题
前端·css
itslife11 分钟前
tsconfig 配置
前端
洋茄子炒鸡蛋12 分钟前
有没有一刻,突然平静地想离职(VUE中使用XLSX插件导入Excel文件,日期解析存在误差)
vue.js·excel
顺丰同城前端技术团队13 分钟前
都2024年了 国际化你都不会做 附使用&源码阅读
前端·javascript
outsider_友人A15 分钟前
手摸手带你封装Vue组件库(16)Carousel走马灯组件
前端·javascript·vue.js
程序员小刚16 分钟前
基于SpringBoot + Vue 的汽车租赁管理系统
vue.js·spring boot·汽车
我是若尘17 分钟前
React 组件渲染机制:Props 变化时会发生什么?
前端
bigyoung17 分钟前
过滤tree数据中某些数据
前端·javascript·vue.js
禄钴钮恩18 分钟前
列表分页转无限滚动,使用Vue-infinite-loading实现
前端
console.log 战略储备局局长20 分钟前
TS类型体操:实现axios的链式调用类型提示
前端