当前位置: 首页 > news >正文

用 vue 组件自定义 v-model, 实现一个 Tab 组件。

效果

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

例子

v-model

我们知道 v-model 是 vue 里面的一个指令,它可以用在 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>

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

相关文章:

  • MicroProfile 1.2新增功能介绍
  • Google瓦片地图算法解析
  • TransactionScope只要一个操作失败,它会自动回滚,Complete表示事务完成
  • 网络流媒体技术及其应用
  • 【安全牛学习笔记】w3af-截断代理
  • 典型Linux发行版内核版本
  • Web开发中的文件上传组件uploadify的使用
  • httpie使用详解
  • 程序猿的日常——SpringMVC系统架构与流程回顾
  • Web监听器导图详解
  • 如何成为好的系统分析员
  • 分布式监控系统Zabbix3.2给异常添加邮件报警
  • 如何保证用户密码安全
  • Lintcode123 Word Search solution 题解
  • The Little Prince-12/08
  • (三)从jvm层面了解线程的启动和停止
  • [case10]使用RSQL实现端到端的动态查询
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • npx命令介绍
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 大整数乘法-表格法
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 前端设计模式
  • 全栈开发——Linux
  • 数组大概知多少
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ​Linux·i2c驱动架构​
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (6)STL算法之转换
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (Python) SOAP Web Service (HTTP POST)
  • (二)linux使用docker容器运行mysql
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (详细版)Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (转)scrum常见工具列表
  • (转)拼包函数及网络封包的异常处理(含代码)
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET国产化改造探索(三)、银河麒麟安装.NET 8环境
  • .Net中ListT 泛型转成DataTable、DataSet
  • @RequestParam,@RequestBody和@PathVariable 区别
  • [ 转载 ] SharePoint 资料
  • [acwing周赛复盘] 第 69 场周赛20220917
  • [AIGC] Redis基础命令集详细介绍
  • [LeetCode] 196. 删除重复的电子邮箱
  • [Linux]history 显示命令的运行时间