如何写一个双向绑定的VUE组件

最近在重构公司项目代码,因为之前这一块不是我写的,所以也抽空看了一下之前同事的代码,可能是因为大家都是初学VUE,现在看来,有些代码还是略显稚嫩的,有一个小组件,虽然很简单,但是还是可以拿出来说一下的,因为它是一个双向绑定组件.

v-model是什么

教程上说的很清楚,v-model是一个语法糖,它是由 :value@input构成的[1]

为什么我需要v-model

使用v-model不仅仅是因为它简单,它的意义一方面是简化了书写,二是简化了逻辑,三是更加语义化,四是让API更加友好,简单易懂.
在VUE2.5之后,.sync修饰符又重新回归了,我们可以通过.sync修饰符来处理双向绑定问题,但是我觉得两者的使用场景是不一样的,v-model更多的是接收用户输入,而.sync更多的是同步数据.

怎么样实现一个可以使用v-model的组件

其实这个问题也很简单,只要仔细思考了上面两个问题,应该可以知道如何写,下面我就拿项目中的这个例子来做一个介绍,这个组件的效果是这样的,一个简单的switch开关:

QQ20180429-133843-HD.gif-66.8kB
QQ20180429-133843-HD.gif-66.8kB

改造之前的代码:

<template>
    <div class="switch-outer"
         :class="{'on':on,'off':!on}"
         @click="handleChange">
        <span class="text"
              v-show="on">{{onText}}</span>
        <span class="text"
              v-show="!on">{{offText}}</span>
        <span class="active-ball"></span>
    </div>
</template>
<script>
export default {
    data() {
        return {
            on: this.isOn
        };
    },
    props: {
        onText: {
            type: String,
            default: '开启'
        },
        offText: {
            type: String,
            default: '关闭'
        },
        isOn: {
            type: Boolean,
            default: true
        }
    },
    methods: {
        handleChange() {
            this.on = !this.on;
            this.$emit('change', this.on);
        }
    },
    watch: {
        isOn() {
            this.on = this.isOn;
        }
    }
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.switch-outer {
    position: absolute;
    cursor: pointer;
    color: #ffffff;

    .active-ball {
        width: 28px;
        height: 28px;
        border-radius: 50%;
        background-color: currentColor;
        position: absolute;
        top: 1px;
    }
}

.on {
    background: #00a4ee;

    .active-ball {
        left: 53px;
        transition: all 0.5s ease;
    }

    .text {
        margin-left: 14px;
        font-size: 14px;
    }
}

.off {
    background: #c0ccda;

    .active-ball {
        left: 2px;
        transition: all 0.5s ease;
    }

    .text {
        margin-left: 44px;
        font-size: 14px;
    }
}
</style>

改造之前的代码,并不能使用v-model来做双向绑定,但是很显然,这是一个switch开关,是一个用户输入组件,所以使用v-model是很自然的需求,但是目前这个组件的使用方式是这样的

 <sky-switch class="share-switch"
            @change="handleChange"
            :isOn="group.is_files_share"
            ref="shareSwitch"></sky-switch>
// 手动监听change事件,修改数据
handleChange(on) {
    this.group.is_files_share = on;
}

我们需要自己监听change事件,手动的修改值,这显然不符合我们对用户输入组件的认知,我们需要一个像<input>那样的组件,通过 v-model 来绑定数据,剩下的交给vue来帮助我们处理.

显然这种使用方式并不友好,不能称为一个合格的组件.如果没有文档,我需要看源码才能知道这个组件如何使用,如果源码写的十分复杂,这个组件基本上是不可复用的.

接下来我们就开始魔改这个组件代码,删除不必要的监听和属性,简单直接的利用v-model来实现这个功能

改造后的代码

<template>
    <div class="switch-outer"
         :class="{'on':on,'off':!on}"
         @click="handleChange">
        <span class="text"
              v-show="on">{{onText}}</span>
        <span class="text"
              v-show="!on">{{offText}}</span>
        <span class="active-ball"></span>
    </div>
</template>
<script>
export default {
    props: {
        onText: {
            type: String,
            default: '开启'
        },
        offText: {
            type: String,
            default: '关闭'
        },
        value: {  // 将 isOn改成 value ,用来接收 v-model 的传值
            type: Boolean,
            default: true
        }
    },
    methods: {
        handleChange() {
            this.on = !this.on;  // 通过赋值操作来触发属性 on 的 set 方法
            this.$emit('change', this.on); // 发送 change 事件,作为一个事件钩子,方便用户处理自己的逻辑
        }
    },
    computed: {
        on: {  // 去掉了data里面的on,使用计算属性
            get() {  // get时返回 value值
                return this.value; 
            },
            set(val) { // set时发送 input 事件
                this.$emit('input', val);
            }
        }
    }
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.switch-outer {
    position: absolute;
    cursor: pointer;
    color: #ffffff;
    .active-ball {
        width: 28px;
        height: 28px;
        border-radius: 50%;
        background-color: currentColor;
        position: absolute;
        top: 1px;
        transition: all 0.5s ease;
    }
    transition: all 0.5s ease;
}

.on {
    background: #00a4ee;
    .active-ball {
        left: 53px;
        transition: all 0.5s ease;
    }
    .text {
        margin-left: 14px;
        font-size: 14px;
        transition: all 0.5s ease;
    }
}

.off {
    background: #c0ccda;
    .active-ball {
        left: 2px;
    }
    .text {
        margin-left: 44px;
        font-size: 14px;
        transition: all 0.5s ease;
    }
}
</style>

这段代码的核心是那个计算属性on,巧妙的通过计算属性的getset方法,实现监听数据变化,get时返回通过props传入的value,set时,向父组件发送input事件,这样就可以配合v-model来使用了,因为v-model的本质就是向组件传一个valueprop,并且监听input方法[2]

style部分做了微调,因为这里加入了动画,但是原来的动画只有部分值是动的,会显得很突兀,所以调整为所有变化的属性都有动画,这样整个动画变得很平滑.

改造后的组件,使用方式是这样的:

<sky-switch class="share-switch" v-model="group.share_file"></sky-switch>

简单明了,一看就是一个用户输入组件,和<input>元素一样.

当然,这个组件还不够完善,API接口也没有很好的设计,但是用来说明v-model这个问题应该是足够了.

写在最后

没事多重构自己的点,也可以重构别人的代码,看看别人是怎么实现的,自己又能怎么实现,相互对比,学习,这样才能快速进步.
前两天有事情,清了一天假,项目进度有点落后,今天过来补点进度,剩下点时间,写了这篇文字,虽然是一个很小的点,但是,希望能对对个别人有所启发.


  1. 这里只是简单的说,其实由什么属性和事件组成,可以通过一个model参数来定制

  2. 这里依然是从简单的角度来说.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容