视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
利用vue组件自定义v-model实现一个Tab组件方法示例
2020-11-27 22:24:16 责编:小采
文档


前言

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

效果

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


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>

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

总结

下载本文
显示全文
专题