当前位置: 首页 > 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
  • 【Leetcode】101. 对称二叉树
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 《剑指offer》分解让复杂问题更简单
  • Apache Zeppelin在Apache Trafodion上的可视化
  • Cookie 在前端中的实践
  • cookie和session
  • github从入门到放弃(1)
  • Github访问慢解决办法
  • js算法-归并排序(merge_sort)
  • Kibana配置logstash,报表一体化
  • leetcode-27. Remove Element
  • Sass Day-01
  • WebSocket使用
  • win10下安装mysql5.7
  • 分享几个不错的工具
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 排序算法之--选择排序
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 网页视频流m3u8/ts视频下载
  • 微信支付JSAPI,实测!终极方案
  • 小程序开发之路(一)
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #考研#计算机文化知识1(局域网及网络互联)
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (论文阅读40-45)图像描述1
  • (实战篇)如何缓存数据
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (已解决)什么是vue导航守卫
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .Net Core和.Net Standard直观理解
  • .net framework profiles /.net framework 配置
  • .Net MVC + EF搭建学生管理系统
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • @Service注解让spring找到你的Service bean