利用vue组件自定义v-model实现一个Tab组件方法示例


Posted in Javascript onDecember 06, 2017

前言

最近在学习vue,今天看到自定义组件,纠结了一会会然后恍然大悟...官方教程写得不是很详细,所以我决定总结一下。下面话不多说了,来一起看看详细的介绍吧。

效果

先让我们看一下例子的效果吧!

利用vue组件自定义v-model实现一个Tab组件方法示例
v-model

我们知道 v-model 是 vue 里面的一个指令,vue的v-model是一个十分强大的指令,它可以自动让原生表单组件的值自动和你选择的值绑定,它可以用在 input 标签上,来做数据的双向绑定,就像这样:

<input v-model="tab">

v-model 事实上是一个语法糖,你也可以这么写:

<input :value="tab" :input="tab = $event.target.value">

可以看得出来,就是传进去一个参数 :value,监听一个事件 @input 而已。

如果有这样的需求,需要在自己的组件上使用 v-model,就像这样:

<Tab v-model="tab"></Tab>

如何来实现呢?

既然已经知道 v-model 是语法糖了,那么首先,我们可以知道在组件内得到的参数。

<!-- Tab.vue -->
<template>
 <div class="tab">
  <p>可以试着把这个值打印出来???</p>
  {{value}}
 </div>
</template>


<script>
 export default {
  props: {
   // ↓这个就是我们能取到的参数
   value: {
    type: String,
    default: ''
   }
  }
 }
</script>

嗯,先把这个 value 先放着,如果要实现例子的那个 Tab,还需要传进来一组选项(options):

<!-- example.vue -->
<template>
 <div>
  <!-- 这里多了一个参数 ↓ -->
  <Tab v-model="tab" :options="options"></Tab>
  <p class="info">{{tab}}</p>
 </div>
</template>

<script>
 import Tab from '~/Tab';

 export default {
  components: {
   Tab
  },
  data() {
   return {
    tab: 'bj',
    options: [{
     value: 'bj',
     text: '北京'
    }, {
     value: 'sh',
     text: '上海',
     disabled: true
    }, {
     value: 'gz',
     text: '广州'
    }, {
     value: 'sz',
     text: '深圳'
    }]
   }
  }
 }
</script>

那我们就把传进来的 options 循环出来吧!

<!-- Tab.vue -->
<template>
 <div class="tab">
  <div 
   class="item"
   v-for="(item, i) in options"
   :key="i">
   {{item.text}}
  </div>
 </div>
</template>

<script>
 export default {
  props: {
   value: {
    type: String
   },
   options: {
    type: Array,
    default: []
   }
  }
 }
</script>

传进来的 options 缺少些参数,我们每个选项需要 active 来标记是否是选中状态,需要 disabled 来标记是否是禁选状态,所以拷贝一个 currOptions 来补全不足参数。

另外直接改变 value 这个 props 是没有效果滴,所以拷贝一个 value 的副本,叫 currValue。

<!-- Tab.vue -->
<script>
 export default {
  props: {
   value: {
    type: String
   },
   options: {
    type: Array,
    default: []
   }
  },
  data() {
   return {
    // 拷贝一个 value
    currValue: this.value,
    currOptions: []
   }
  },
  mounted() {
   this.initOptions();
  },
  methods: {
   initOptions() {
    // 拷贝一个 options
    this.currOptions = this.options.map(item => {
     return {
      ...item,
      active: item.value === this.currValue,
      disabled: !!item.disabled
     }
    });
   }
  }
 }
</script>

?接下来再在选项上绑定击事件就 OK 了。

既然知道父组件会接受 input 事件,那我们就只需要 this.$emit('input', this.currValue); 就好了。

<!-- Tab.vue -->
<template>
 <div class="tab">
  <div 
   class="item"
   v-for="(item, i) in options"
   :key="i"
   @click="onTabSelect(item)">
   <!-- ↑ 这里绑定了一个事件! -->
   {{item.text}}
  </div>
 </div>
</template>

<script>
 export default {
  props: {
   value: {
    type: String
   },
   options: {
    type: Array,
    default: []
   }
  },
  data() {
   return {
    currValue: this.value,
    currOptions: []
   }
  },
  mounted() {
   this.initOptions();
  },
  methods: {
   initOptions() {
    this.currOptions = this.options.map(item => {
     return {
      ...item,
      active: item.value === this.currValue,
      disabled: !!item.disabled
     }
    });
   },
   // 添加选中事件
   onTabSelect(item) {
    if (item.disabled) return;
    this.currOptions.forEach(obj => obj.active = false);
    item.active = true;
    this.currValue = item.value;
    // 发布 input 事件,↓ 父组件如果有 v-model 就会监听到的。
    this.$emit('input', this.currValue);
   }
  }
 }
</script>

剩下的补上点样式还有 watch 下 value 和 options 的变化就可以了,最后贴上完整代码。

完整代码

<!-- example.vue -->
<template>
 <div>
  <Tab v-model="tab" :options="options"></Tab>
  <p class="info">{{tab}}</p>
 </div>
</template>

<script>
 import Tab from '~/Tab';

 export default {
  components: {
   Tab
  },
  data() {
   return {
    tab: 'bj',
    options: [{
     value: 'bj',
     text: '北京'
    }, {
     value: 'sh',
     text: '上海',
     disabled: true
    }, {
     value: 'gz',
     text: '广州'
    }, {
     value: 'sz',
     text: '深圳'
    }]
   }
  }
 }
</script>

<style lang="less" scoped>
 .info {
  margin-left: 50px;
  font-size: 30px;
 }
</style>
<!-- Tab.vue -->
<template>
 <div class="tab">
  <div 
   class="item"
   v-for="(item, i) in currOptions"
   :class="item | tabItemClass"
   :key="i"
   @click="onTabSelect(item)">
   {{item.text}}
  </div>
 </div>
</template>

<script>
 export default {
  props: {
   value: {
    type: String
   },
   options: {
    type: Array,
    default: []
   }
  },
  data() {
   return {
    currValue: this.value,
    currOptions: []
   }
  },
  mounted() {
   this.initOptions();
  },
  methods: {
   initOptions() {
    this.currOptions = this.options.map(item => {
     return {
      ...item,
      active: item.value === this.currValue,
      disabled: !!item.disabled
     }
    });
   },
   onTabSelect(item) {
    if (item.disabled) return;
    this.currOptions.forEach(obj => obj.active = false);
    item.active = true;
    this.currValue = item.value;
    this.$emit('input', this.currValue);
   }
  },
  filters: {
   tabItemClass(item) {
    let classList = [];
    if (item.active) classList.push('active');
    if (item.disabled) classList.push('disabled');
    return classList.join(' ');
   }
  },
  watch: {
   options(value) {
    this.initOptions();
   },
   value(value) {
    this.currValue = value;
   }
  }
 }
</script>

<style lang="less" scoped>
 .tab {
  @borderColor: #ddd;
  @radius: 5px;

  width: 100%;
  margin: 50px;
  overflow: hidden;
  position: relative;
  .item {
   padding: 10px 50px;
   border-top: 1px solid @borderColor;
   border-left: 1px solid @borderColor;
   border-bottom: 1px solid @borderColor;
   font-size: 30px;
   background-color: #fff;
   float: left;
   user-select: none;
   cursor: pointer;
   transition: 300ms;
   &:first-child {
    border-top-left-radius: @radius;
    border-bottom-left-radius: @radius;
   }
   &:last-child {
    border-right: 1px solid @borderColor;
    border-top-right-radius: @radius;
    border-bottom-right-radius: @radius;
   }
   &.active {
    color: #fff;
    background-color: red;
   }
   &:hover {
    color: #fff;
    background-color: #f06;
   }
   &.disabled {
    color: #fff;
    background-color: pink;
    cursor: no-drop;
   }
  }
 }
</style>

最后送上官网的链接→ 传送门

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
Javascript 判断Flash是否加载完成的代码
Apr 12 Javascript
JS在TextArea光标位置插入文字并实现移动光标到文字末尾
Jun 21 Javascript
js限制checkbox选中个数以限制六个为例
Jul 15 Javascript
js与jquery实时监听输入框值的oninput与onpropertychange方法
Feb 05 Javascript
解决js图片加载时出现404的问题
Nov 30 Javascript
JQuery组件基于Bootstrap的DropDownList(完整版)
Jul 05 Javascript
Node.js的特点详解
Feb 03 Javascript
详解webpack3如何正确引用并使用jQuery库
Aug 26 jQuery
jQuery实现轮播图效果
Nov 26 jQuery
24个解决实际问题的ES6代码片段(小结)
Feb 02 Javascript
vue实现移动端H5数字键盘组件使用详解
Aug 25 Javascript
Vue自定义组件双向绑定实现原理及方法详解
Sep 03 Javascript
如何重置vue打印变量的显示方式
Dec 06 #Javascript
微信小程序实现点击按钮修改view标签背景颜色功能示例【附demo源码下载】
Dec 06 #Javascript
vue cli使用绝对路径引用图片问题的解决
Dec 06 #Javascript
微信小程序实现点击按钮移动view标签的位置功能示例【附demo源码下载】
Dec 06 #Javascript
实现单层json按照key字母顺序排序的示例
Dec 06 #Javascript
Thinkjs3新手入门之如何使用静态资源目录
Dec 06 #Javascript
浅谈JsonObject中的key-value数据解析排序问题
Dec 06 #Javascript
You might like
深入探讨PHP中的内存管理问题
2011/08/31 PHP
从零开始学YII2框架(二)通过 Composer 安装扩展插件
2014/08/20 PHP
php使用ZipArchive提示Fatal error: Class ZipArchive not found in的解决方法
2014/11/04 PHP
PHP的PDO连接讲解
2019/01/24 PHP
laravel框架使用极光推送消息操作示例
2020/02/15 PHP
[原创]用javascript实现检测指定目录是否存在的方法
2008/01/12 Javascript
实现JavaScript中继承的三种方式
2009/10/16 Javascript
用JQuery在网页中实现分隔条功能的代码
2012/08/09 Javascript
输入框过滤非数字的js代码
2014/09/18 Javascript
深入理解JavaScript系列(33):设计模式之策略模式详解
2015/03/03 Javascript
canvas雪花效果核心代码分享
2017/02/19 Javascript
详解从Vue.js源码看异步更新DOM策略及nextTick
2017/10/11 Javascript
vue中锚点的三种方法
2018/07/06 Javascript
vue.js中ref和$refs的使用及示例讲解
2019/08/14 Javascript
[03:22]DAC最前线(第二期)—DOTA2亚洲邀请赛主赛场周边及线路探访
2015/01/24 DOTA
浅析Python中的多进程与多线程的使用
2015/04/07 Python
教你使用python画一朵花送女朋友
2018/03/29 Python
Django项目中包含多个应用时对url的配置方法
2018/05/30 Python
python实现图片筛选程序
2018/10/24 Python
Python 函数list&amp;read&amp;seek详解
2019/08/28 Python
Python 使用office365邮箱的示例
2020/10/29 Python
Python 删除List元素的三种方法remove、pop、del
2020/11/16 Python
美国背景检查、公共记录和人物搜索网站:BeenVerified
2018/02/25 全球购物
法国一家芭蕾舞鞋公司:Repetto
2018/11/12 全球购物
Lovedrobe官网:英国领先的大码服装品牌
2019/09/19 全球购物
总裁秘书岗位职责
2013/12/04 职场文书
教师对学生的评语
2014/04/28 职场文书
捐款倡议书格式范文
2014/05/14 职场文书
自我检讨报告
2015/01/28 职场文书
挂职锻炼个人总结
2015/03/05 职场文书
农村党支部承诺书
2015/04/30 职场文书
党员理论学习心得体会
2016/01/21 职场文书
2019大学毕业晚会主持词
2019/06/21 职场文书
Go语言切片前或中间插入项与内置copy()函数详解
2021/04/27 Golang
MySQL 表锁定 LOCK和UNLOCK TABLES的 SQL语法
2022/04/18 MySQL
TS 类型兼容教程示例详解
2022/09/23 Javascript